Strategy pattern

Imagine the following scenario: we have some customer data that needs to be exported to an xml file somewhere. A solution could look like this:

public class Customer
{
    public Customer(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public void Export()
{
    // retrieve the data
    var customers = new List<Customer>();
    customers.Add(new Customer("Walter", "Jones"));
    customers.Add(new Customer("Timothy", "Dawson"));
    customers.Add(new Customer("Michael", "Boyce"));

    // Convert data to xml
    var xml = new XElement("customers",
        from c in customers
        select new XElement("customer",
            new XElement("firstName", c.FirstName),
            new XElement("lastName", c.LastName)
        ));
    var encodedDoc8 = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), xml);      

    // Create xml file
   encodedDoc8.Save(@"D:\temp\customers.xml");            
}

Nothing wrong with this approach, and the generated xml file looks fine:

But now let’s imagine that the generated xml should instead be exported to an ftp server, or the data should be formatted as a comma separated file, or maybe sent to an external service that processes it further. We would need to rewrite this method, or duplicate functionality, to support this. We don’t have the flexibility to easily replace or reuse functionality with this procedural approach.

Now the strategy pattern comes to the rescue!

First, we consider the export scenario to be a strategy. The export strategy consists of three steps:

  • Retrieve the data
  • Format the data
  • Publish the data in the new format

Each step of the strategy is a separate responsibility, which means that we are going to define an interface for each responsibility:

And for each interface, we will (at this time) have only one real implementation:

The MockRetriever implementation will generate some mock data, but later another retriever can be added that reads the data from a database.

The XmlFormatter implementation will take the data and formats it as XML, and later another publisher can be added that formats the data to a comma-separated format.

The FilePublisher implementation will take the formatted data and publish it as a file to some location on a hard drive, and later another publisher can be added that publishes the data somewhere on an FTP server.

So, in code, first add the interfaces:

public interface IRetriever
{
    IList<Customer> Retrieve();
}

public interface IFormatter
{
    MemoryStream Format(IList<Customer> customers);
}

public interface IPublisher
{
    void Publish(MemoryStream data);
}

And then the real implementations:

public class MockRetriever : IRetriever
{
    public IList<Customer> Retrieve()
    {
        var customers = new List<Customer>();
        customers.Add(new Customer("Walter", "Jones"));
        customers.Add(new Customer("Timothy", "Dawson"));
        customers.Add(new Customer("Michael", "Boyce"));
        return customers;
    }
}

public class XmlFormatter : IFormatter
{
    public MemoryStream Format(IList<Customer> customers)
    {
        // Convert data to xml
        var xml = new XElement("customers",
            from c in customers
            select new XElement("customer",
                new XElement("firstName", c.FirstName),
                new XElement("lastName", c.LastName)
            ));
        var encodedDoc8 = new XDocument(
                  new XDeclaration("1.0", "utf-8", "yes"), xml);
        var memoryStream = new MemoryStream();
        var xmlWriterSettings = new XmlWriterSettings 
                  { OmitXmlDeclaration = false, Indent = true };
        using (var xw = XmlWriter.Create(memoryStream, xmlWriterSettings)) 
                  { encodedDoc8.WriteTo(xw); }
        return memoryStream;
    }
}

public class FilePublisher : IPublisher
{
    public void Publish(MemoryStream data)
    {
        var fileToCreate = @"D:\temp\customers.xml";
        using (var outStream = File.OpenWrite(fileToCreate))
        {
            data.WriteTo(outStream);
        }
    }
}

Note that the formatter returns a memorystream, and the publisher takes a memorystream; to make them generic.

And now we can write the interface and implementation of the export strategy itself:

public interface IExportStrategy
{
    void Start();
}

public class ExportStrategy : IExportStrategy
{
    private IRetriever _retriever;
    private IFormatter _formatter;
    private IPublisher _publisher;

    public ExportStrategy(IRetriever retriever, 
            IFormatter formatter, IPublisher publisher)
    {
       _retriever = retriever;
       _formatter = formatter;
       _publisher = publisher;
    }

    public void Start()
    {
       var customers = _retriever.Retrieve();
       var formattedData = _formatter.Format(customers);
       _publisher.Publish(formattedData);
    }
}

Starting the export strategy is now easy and flexible: just register all real implementations with your favorite dependency injection framework, and then call the Start method of the export strategy. Using  another retriever, formatter or publisher is very easy to do: just register the ones you want to be used. Responsabilities are very clear, and you can easily test and mock each part of the strategy.

Another example

Suppose you have some cleanup mechanism, that needs a setting that comes from the configuration file (or a default one if it was not found in the configuration file):

public class Prices
{
public void CleanupPrices()
{
// Get number of days
var numberOfDaysConfig = ConfigurationManager.AppSettings["NumberOfDays"];
int numberOfDays;
if ((numberOfDaysConfig == null)
|| (!Int32.TryParse(numberOfDaysConfig, out numberOfDays)))
{
// some default value
numberOfDays = 7;
}

// Start cleanup here
}
}
 
To maximize reuse and flexibility, we could change this into a resolver:
 
public interface INumberOfDaysResolver
{
int Resolve();
}

public class NumberOfDaysResolver : INumberOfDaysResolver
{
public int Resolve()
{
// Get number of days
var numberOfDaysConfig = ConfigurationManager.AppSettings["NumberOfDays"];
int numberOfDays;
if ((numberOfDaysConfig == null)
|| (!Int32.TryParse(numberOfDaysConfig, out numberOfDays)))
{
// some default value
numberOfDays = 7;
}
return numberOfDays;
}
}

Then you can have it injected as follows:

public class Prices
{
private readonly INumberOfDaysResolver _numberOfDaysResolver;

public Prices(INumberOfDaysResolver numberOfDaysResolver)
{
_numberOfDaysResolver = numberOfDaysResolver;
}

public void CleanupPrices()
{
// Get number of days
var numberOfDaysConfig = _numberOfDaysResolver.Resolve();

// Start cleanup here
}
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s