Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Strategy Pattern at work

0.00/5 (No votes)
12 Mar 2010CPOL3 min read 1  
We recently ran into a challenging design issue at work...

We recently ran into a challenging design issue at work. We are working on a web application that must support internationalization since we have customers in many different countries. The profile for each customer has different settings and configurations based upon the customer’s country.

These settings/configurations overlap and differ across countries. The goal, of course, is to design/implement the code in a way that enables us to reuse code that is common across countries. Below is the scenario that we are dealing with:

  • USCustomer.FeatureA() is the same as CACustomer.FeatureA()
  • USCustomer.FeatureB() is the same as KRCustomer.FeatureB()
  • CACustomer.FeatureC() is the same as KRCustomer.FeatureC()
  • KRCustomer.FeatureA() differs from USCustomer.FeatureA()
  • CACustomer.FeatureB() differs from USCustomer.FeatureB()
  • USCustomer.FeatureC() differs from CACustomer.FeatureC()

The first attempt was of course to try and solve this via sub-classing. Let’s look at the different options that we have for sub-classing:

  1. Make CA & KR sub-classes of US. This allows us to re-use USCustomer.FeatureA() for CA and USCustomer.FeatureB() for KR. But it doesn't allow us to re-use CACustomer.FeatureC() for KR.
  2. Okay, no problem, you say. We'll just make KR a sub-class of CA. So, CA is a sub-class of US and KR is a sub-class of CA. But then we run into the issue that USCustomer.FeatureB() can no longer to be re-used for KRCustomer.FeatureB().

Now imagine this kind of situation spread across 15 different countries. Ouch! So sub-classing clearly is not the answer.

It sounds like we need some sort of “strategy” that'll let us swap/differ settings and configurations across countries without all this coupling. Well the strategy is to apply the Strategy design pattern. Below is the overall strategy:

  1. Encapsulate the features: Each feature gets its own class. So we end up with the following classes:
    Customer
    CACustomer
    KRCustomer
    FeatureA : IFeatureA
    FeatureB : IFeatureB
    FeatureC : IFeatureC
    FeatureA1 : IFeatureA
    FeatureB1 : IFeatureB
    FeatureC1 : IFeatureC
  2. Setup the constructor of the Customer class to take in the concrete class that is specific to the feature that they require as a parameter. For instance:
    C++
    public class Customer
    {
       public Customer() : 
         this(new FeatureA(), new FeatureB(), new FeatureC())
       {  }
     
       public Customer(IFeatureA a, IFeatureB b, IFeatureC c)
       {
          featureA = a;
          featureB = b;
          featureC = c;
       }
     
       public void DoInitialSetup()
       {
          SetupFeatureA(featureA);
          SetupFeatureB(featureB);
          SetupFeatureC(featureC);
       }
     
       private IFeatureA featureA;
       private IFeatureB featureB;
       private IFeatureC featureC;
    }
     
    public class CACustomer : Customer
    {
       public CACustomer() : 
         base(new FeatureA(), new FeatureB1(), new FeatureC1())
       {  }
    }
     
    public class KRCustomer : Customer
    {
       public KRCustomer() : 
         base(new FeatureA1(), new FeatureB(), new FeatureC1())
      {  }
    }

And voila! We can now swap implementations across countries and add different implementations for different countries as needed.

The above example was simplified to effectively demonstrate the Strategy pattern without confusing the reader. In particular, in the above example, the customer classes directly instantiate the features that they need. This is generally a bad idea in practice for at least two reasons:

  1. It makes it difficult to unit test the Customer class.
  2. Each type of customer is still coupled to the specific feature-set that it is using. In other words, the features cannot be changed dynamically.

Instead of directly instantiating the classes, we should be using an IOC container to handle the object instantiations and configurations.

For instance, for our application, we are using an XML file to tie concrete implementations with their respective countries. Below is an example:

DefaultLocalization.xml

XML
<interface name="IFeatureA">
  <class name="FeatureA">
</interface>
 
<interface name="IFeatureB">
  <class name="FeatureB">
</interface>
 
<interface name="IFeatureC">
  <class name="FeatureC">
</interface>

CALocalization.xml

XML
<interface name="IFeatureB">
  <class name="FeatureB1">
</interface>
 
<interface name="IFeatureC">
  <class name="FeatureC1">
</interface>

KRLocalization.xml

XML
<interface name="IFeatureA">
  <class name="FeatureA1">
</interface>
 
<interface name="IFeatureC">
  <class name="FeatureC1">
</interface>

Based on the current country, the right classes get instantiated. If we ever need to change KR to use FeatureC instead of FeatureC1, all we do is make the change in the XML file and we’re done.

So, in conclusion, the key to the strategy pattern is encapsulation: Remove the logic that differs from the logic that stays the same via encapsulation.

Post to Twitter Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to Reddit Reddit Post to StumbleUpon

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)