A simple rules engine

I'm extracting data from some OCR'd letters and, in order to determine which type of letter I'm parsing, I'm using a method similar to this:

        public Letter Parse(string text)
        {
            Letter result;
            if (text.IndexOf("...", StringComparison.OrdinalIgnoreCase) >= 0)
                letter = new LetterA();
            else
                letter = new LetterB();

            //... additional processing
            return letter;
        }

If the letter contains a specific text, I know it's of one type; otherwise I'll default to the other type. Unfortunately that's going to get really complicated, really fast once I start adding new letter types. I read somewhere that "you should move logic out of the code and into the data when possible"; it made sense and I never had a reason to regret it. So, let me try to do that here.

First I'll add a "rules list" class that will allow me to store the various criteria:

    public class RulesList<T, TResult>
    {
        public RulesList()
        {
            rules = new List<Tuple<Predicate<T>, Func<TResult>>>();
        }

        public void Add(Predicate<T> condition, Func<TResult> constructor)
        {
            rules.Add(Tuple.Create(condition, constructor));
        }

        public TResult Get(T criteria)
        {
            return rules
                .Where(rule => rule.Item1(criteria))
                .Select(rule => rule.Item2())
                .FirstOrDefault();
        }

        //

        private readonly List<Tuple<Predicate<T>, Func<TResult>>> rules;
    }

I've made this class more generic by replacing the string type with T; honestly, I don't think there will ever be a need for anything else in this project but… it wasn't a big "expense".

Using this is quite simple at the moment:

    public class LetterSelector
    {
        public LetterSelector()
        {
            rules = new RulesList<string, Letter>();

            rules.Add(s => s.IndexOf("...", StringComparison.OrdinalIgnoreCase) >= 0, () => new LetterA());
            rules.Add(_ => true, () => new LetterB());
        }

        public Letter Parse(string text)
        {
            var letter = rules.Get(text);
            //... additional processing

            return letter;
        }

        //

        private readonly RulesList<string, Letter> rules;
    }

Is this a big gain? Right now it doesn't look like I gained anything; however, bitter experience taught me that methods with many conditionals quickly become an unmaintainable mess (you haven't lived until you've had to fix a method with 700+ lines and a cyclomatic complexity over 200). This will allow me to separate those conditionals into their own lambdas or small private methods in the LetterSelector class.

Comments

Popular posts from this blog

Posting dynamic Master / Detail forms with Knockout

Comparing Excel files, take two

EF Code First: seeding with foreign keys