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