Software Design Blog - Strategy Pattern Simple solutions to solve complex problems / http://www.rssboard.org/rss-specification BlogEngine.NET 3.1.1.0 en-US /opml.axd http://www.dotnetblogengine.net/syndication.axd Jay Strydom Software Design Blog 0.000000 0.000000 Kill the switch with the strategy pattern <img class="img-responsive" alt="Chain of Responsibility" src="/pics/banners/StrategyPatternBlog.jpg"> <br> <p> The <a href="/post/problem-solving-beyond-the-basics">previous post</a> described the challenge of validating credit card numbers based on the credit card type such as visa or master card. Many if conditional checks were performed which was improved using the switch statement. </p> <h5>Why was the switch statement bad?</h5> <p>Let's say you have 5 cases and 10 lines each.</p> <ul> <li>What if we had to add, remove or modify a case? The class has 7 reasons to change, which violates the open/close principle.</li> <li>At least 5 tests are required to test each case. The class does 5 things, so it violates the single responsibility principle.</li> <li>The class is at least 50 lines long without the possibility of reusing anything.</li> </ul> <p> This post will provide an illustration of using the strategy design pattern with table control flow to replace the switch statement. </p> <p><b>The problem</b> is how we can define a family of algorithms (credit card validators), encapsulate each one, and make them interchangeable?</p> <p><b>The solution</b> is to use the strategy design pattern that allows the algorithm to vary independently from clients that use it.</p> <a class="btn btn-primary btn-sm" role="button" href="/Downloads/TableFlowStrategyPattern.zip">Download Source Code</a> <h3>Setup</h3> The following classes from the <a href="/post/problem-solving-beyond-the-basics">previous post</a> will be reused. <pre class="brush: c-sharp;"> public class CreditCard { public string Type { get; set; } public string Name { get; set; } public string Number { get; set; } public string Expiry { get; set; } } public interface ICreditCardValidator { void Validate(CreditCard creditCard); } </pre> <h3>Strategy Pattern</h3> <p>Let's get started with the strategy pattern in 5 easy steps.</p> <h5>1. Specify the signature for the algorithm in an interface</h5> <p>We need to validate a credit card number so we need a method that takes a number as shown below.</p> <pre class="brush: c-sharp;"> public interface ICreditCardNumberValidator { void Validate(string number); } </pre> <h5>2. Bury the algorithm implementation details in derived classes</h5> <p>Each credit card validator implementation can be different. In this example, all credit card validators will use a regular expression so we only need one class.</p> <pre class="brush: c-sharp;"> public class RegExCreditCardNumberValidator : ICreditCardNumberValidator { private readonly Regex _regex; public RegExCreditCardNumberValidator(Regex regex) { if (regex == null) throw new ArgumentNullException("regex"); _regex = regex; } public void Validate(string number) { if (!_regex.IsMatch(number)) throw new InvalidCreditCardException(); } } </pre> <p>A new instance of the class will be created for each credit card type. For example:</p> <pre class="brush: c-sharp;"> var visaRegEx = new Regex("^4[0-9]{6,}$", RegexOptions.Compiled); var visaValidator = new RegExCreditCardNumberValidator(visaRegEx); </pre> <h5>3. Identify an algorithm that the client would prefer to access</h5> <p>The credit card validation algorithm is based on the credit card type. The credit card type is supplied in the credit card model/DTO. Therefore, we can perform an algorithm lookup based on the credit card type.</p> <p>A dictionary is great way to perform a lookup, where the key represents the credit card type and the value represents the validator.</p> <pre class="brush: c-sharp;"> var strategies = new Dictionary&lt;string, ICreditCardNumberValidator&gt; (StringComparer.OrdinalIgnoreCase); </pre> <div class="alert alert-success" role="alert"> <b>Solved!</b> The dictionary key lookup is set to case insensitive. The problem of comparing strings or calling .ToLower() in the Switch solution disappears. </div> <p>The strategy validator is shown below.</p> <pre class="brush: c-sharp;"> public class CreditCardValidatorStrategy : ICreditCardValidator { private readonly IDictionary&lt;string, ICreditCardNumberValidator&gt; _strategies; public CreditCardValidatorStrategy( IDictionary&lt;string, ICreditCardNumberValidator&gt; strategies) { if (strategies == null) throw new ArgumentNullException("strategies"); _strategies = strategies; } public void Validate(CreditCard creditCard) { if (creditCard == null) throw new ArgumentNullException("creditCard"); if (string.IsNullOrWhiteSpace(creditCard.Type)) throw new ArgumentException(Resource.MissingCreditCardType); if (!_strategies.ContainsKey(creditCard.Type)) throw new InvalidCardException( string.Format(Resource.UnsupportedCreditCard, creditCard.Type)); _strategies[creditCard.Type].Validate(creditCard.Number); } } </pre> <div class="alert alert-success" role="alert"> <b>Solved!</b> The validator class is agnostic of the credit card validation implementation and doesn't need to be modified in order to support additional credit cards. </div> <h5>4. Building the table control lookup</h5> <p>We can build the dictionary lookup table based on a repository such as a database or a configuration file.</p> <p>The credit card types and regular expressions are defined in the App.config as shown below.</p> <pre class="brush: xml;"> &lt;?xml version="1.0" encoding="utf-8" ?&gt; &lt;configuration&gt; &lt;configSections&gt; &lt;section name="creditcardnumberexpressions" type="System.Configuration.DictionarySectionHandler" /&gt; &lt;/configSections&gt; &lt;creditcardnumberexpressions&gt; &lt;add key="visa" value="^4[0-9]{6,}$" /&gt; &lt;add key="mastercard" value="^5[1-5][0-9]{5,}$" /&gt; &lt;/creditcardnumberexpressions&gt; &lt;/configuration&gt; </pre> <p>We can compose the dictionary of strategies from the App.config as shown below.</p> <pre class="brush: c-sharp;"> public class ConfigDictionaryLoader : ISettingsLoader { private readonly string _sectionName; public ConfigDictionaryLoader(string sectionName) { if (sectionName == null) throw new ArgumentNullException("sectionName"); _sectionName = sectionName; } public IDictionary&lt;string, string&gt; Load() { var settings = ConfigurationManager.GetSection(_sectionName) as Hashtable; if (settings == null) { throw new Exception(string.Format(Resource.MissingConfig, _sectionName)); } return settings.Cast&lt;DictionaryEntry&gt;() .ToDictionary(n =&gt; n.Key.ToString(), n =&gt; n.Value.ToString()); } } public class CreditCardValidatorFactory : ICreditCardValidatorFactory { private readonly ISettingsLoader _loader; public CreditCardValidatorFactory(ISettingsLoader loader) { if (loader == null) throw new ArgumentNullException("loader"); _loader = loader; } public IDictionary&lt;string, ICreditCardNumberValidator&gt; CreateStrategies() { var cardPairs = _loader.Load(); var strategies = new Dictionary&lt;string, ICreditCardNumberValidator&gt; (StringComparer.OrdinalIgnoreCase); foreach (var pair in cardPairs) { var regEx = new Regex(pair.Value, RegexOptions.Compiled); strategies[pair.Key] = new RegExCreditCardNumberValidator(regEx); } return strategies; } } </pre> <div class="alert alert-success" role="alert"> <b>Solved!</b> The solution in the previous post required a new build to add, remove or modify an algorithm. This problem disappears since values are loaded at run time. </div> <h5>5. Running the solution</h5> The entire solution is wired up as shown below. <pre class="brush: c-sharp;"> var configLoader = new ConfigDictionaryLoader("creditcardnumberexpressions"); var creditCardFactory = new CreditCardValidatorFactory(configLoader); var strategies = creditCardFactory.CreateStrategies(); var validator = new CreditCardValidatorStrategy(strategies); var creditCard = new CreditCard() { Type = "ViSa", Number = "4111111111111111" }; validator.Validate(creditCard); </pre> <h3>Summary</h3> <p>The strategy pattern provides a great alternative to the switch statement.</p> <p>The advantages of the strategy design pattern are:</p> <ul> <li>Reduces long lists of conditions such as If and Switch statements</li> <li>Avoids duplicate code</li> <li>Changes in one class does not require changes in other classes</li> <li>The Open-close principle is achieved since the class is open for extension but closed for modification</li> <li>The Single responsibility principle is achieved since the complexity is encapsulated per strategy</li> <li>Unit testing is simplified since each strategy can be tested in isolation</li> </ul> /post/kill-the-switch-with-the-strategy-pattern [email protected] /post/kill-the-switch-with-the-strategy-pattern#comment /post.aspx?id=9202b6aa-9f69-4bd3-9e02-c69136d7e7c4 Mon, 11 Jan 2016 15:52:00 +1300 Design Patterns Strategy Pattern .NET C# Jay Strydom /pingback.axd /post.aspx?id=9202b6aa-9f69-4bd3-9e02-c69136d7e7c4 1 /trackback.axd?id=9202b6aa-9f69-4bd3-9e02-c69136d7e7c4 /post/kill-the-switch-with-the-strategy-pattern#comment /syndication.axd?post=9202b6aa-9f69-4bd3-9e02-c69136d7e7c4