Click here to Skip to main content
Click here to Skip to main content

Patterns In Practice - Strategy and Composite

, 20 Oct 2010
Rate this:
Please Sign up or sign in to vote.
Behavioural PatternsIn software engineering, behavioural design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication. ScenarioUsing the Strategy and Com

Behavioural Patterns

In software engineering, behavioural design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.

 

Scenario

Using the Strategy and Composite patterns to pull a subset out of a large body of data.

  • Imagine we have an N tier application.
  • In the middle tier, we pull a large data set from the data source or cache.
  • We filter the data set and return a sub set of this data based on a supplied set of criteria.

This is a reasonably common scenario, where a large and expensive database query is run, stored for a period of time and refreshed. The middle tier will return the stored data when queried, rather than going back to the database. The middle tier will therefore have to be able to filter out the required sub set of data, without relying on the back end logic.

 

Basic Implementation

The basic loop for returning a sub set of data could look like this. This is a perfectly reasonable solution for a single check, and could be increased to a couple of checks without any problem.

<!-- code formatted by http://manoli.net/csharpformat/ -->
<span class="kwrd">public</span> List<Person> FindPeople(PersonFind find)
{
    <span class="rem">//Get source data set and create new set for return data</span>
    List<Person> people = PersonCache.GetPeople();
    List<Person> resultList = <span class="kwrd">new</span> List<Person>();

    <span class="rem">//For each person</span>
    <span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
    {
        <span class="rem">//Name Check</span>
        <span class="kwrd">if</span> ((find.FirstName != <span class="kwrd">null</span> && person.FirstName != find.FirstName) 
            || (find.LastName != <span class="kwrd">null</span> && person.LastName != find.LastName))
            <span class="kwrd">continue</span>;

        <span class="rem">//Age Check</span>
        <span class="kwrd">if</span> (find.MinimumAge != <span class="kwrd">null</span> || find.MaximumAge != <span class="kwrd">null</span>)
        {
            <span class="rem">//Calculate Age</span>
            <span class="kwrd">int</span> age = (DateTime.Today - person.DateOfBirth).Days / 365;

            <span class="rem">//Check Age Bands</span>
            <span class="kwrd">if</span> ((find.MinimumAge != <span class="kwrd">null</span> && age < find.MinimumAge) ||
                (find.MaximumAge != <span class="kwrd">null</span> && age >

 

Extended Basic Implementation

This loop has continued the pattern of the first, and added a number of additional checks. As we add checks, the loop continues to grow, and we can end up with a loop that is many hundreds of lines.

<span class="kwrd">public</span> List<Person> FindPeople(PersonFind find)
{
    <span class="rem">//Get source data set and create new set for return data   </span>
    List<Person> people = PersonCache.GetPeople();
    List<Person> resultList = <span class="kwrd">new</span> List<Person>();

    <span class="rem">//For each person</span>
    <span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
    {
        <span class="rem">//Name Check</span>
        <span class="kwrd">if</span> ((find.FirstName != <span class="kwrd">null</span> && person.FirstName != find.FirstName) 
            || (find.LastName != <span class="kwrd">null</span> && person.LastName != find.LastName))
            <span class="kwrd">continue</span>;

        <span class="rem">//Age Check</span>
        <span class="kwrd">if</span> (find.MinimumAge != <span class="kwrd">null</span> || find.MaximumAge != <span class="kwrd">null</span>)
        {
            <span class="rem">//Calculate Age</span>
            <span class="kwrd">int</span> age = (DateTime.Today - person.DateOfBirth).Days / 365;

            <span class="rem">//Check Age Bands</span>
            <span class="kwrd">if</span> ((find.MinimumAge != <span class="kwrd">null</span> && age < find.MinimumAge) ||
                (find.MaximumAge != <span class="kwrd">null</span> && age >


Implementation Issues

On a small scale, this solution works, if it is never going to grow beyond a few checks, then no further action needs to be taken, however, if this loop is going to grow with the system, then we will certainly run into issues if we continue down this path.

  • We will have a large block of unwieldy code.
  • Each piece of condition logic is locked in the loop, unavailable elsewhere in the application, such as when validation is required elsewhere.
  • To test a specific condition, a scenario has to be created that will pass all other conditions. This can be difficult, particularly in cases where there are many hundreds of rules.
  • The rule set is fixed and inflexible. It cannot be changed easily to meet different criteria

 

Strategy Pattern

The strategy pattern is a design pattern that enables algorithms to be selected at runtime, by encapsulating the algorithm in an object. This is achieved by the following:

  • Define a family of algorithms
  • Encapsulate each algorithm in an object
  • Load the appropriate implementation at runtime

 

Identify the Method Signature

To identify the method signature, we need to identify what the recurring pattern is in the code. In this instance, the code performed the following:

  • Take a Person object (From the collection)
  • Take a PersonFind object (Parameter)
  • Perform a check on the Person, using the PersonFind (Result: bool)
  • Perform an action based on the result

From this, we can derive the following method signature:

bool IsValid(Person person, PersonFind find)

 

Implement the Strategy Pattern

Now we have the method signature for our recurring problem, we can use it to build an interface. This interface will define the family of algorithms we will use, as mentioned in the description of the Strategy pattern.

Strategy Pattern

 

Implementation Code

Here we have our interface defined in code, with some stub implementations.

<span class="rem">/// <summary></span>
<span class="rem">/// Basic check interface</span>
<span class="rem">/// </summary></span>
<span class="kwrd">public</span> <span class="kwrd">interface</span> IPersonCheck
{
    <span class="kwrd">string</span> Name { get; }

    <span class="kwrd">bool</span> IsValid(Person person, PersonFind find);
}

<span class="kwrd">public</span> <span class="kwrd">class</span> NameCheck : IPersonCheck [...]

<span class="kwrd">public</span> <span class="kwrd">class</span> GenderCheck : IPersonCheck [...]

<span class="kwrd">public</span> <span class="kwrd">class</span> AgeCheck : IPersonCheck [...]

<span class="kwrd">public</span> <span class="kwrd">class</span> OccupationCheck : IPersonCheck [...]

<span class="kwrd">public</span> <span class="kwrd">class</span> SalaryCheck : IPersonCheck [...]


Calling Code

With this foundation now in place, we can replace our original loop logic with the strategy implementation.

<span class="kwrd">public</span> List<Person> FindPeople(PersonFind find)
{
    List<Person> people = PersonCache.GetPeople();
    List<Person> resultList = <span class="kwrd">new</span> List<Person>();

    <span class="rem">//Get checks to run</span>
    List<IPersonCheck> checks = GetCheckSet();

    <span class="rem">//For each person</span>
    <span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
    {
        <span class="kwrd">bool</span> valid = <span class="kwrd">true</span>;

        <span class="rem">//Perform All Checks</span>
        <span class="kwrd">foreach</span> (IPersonCheck check <span class="kwrd">in</span> checks)
        {
            <span class="rem">//Handle if fail</span>
            <span class="kwrd">if</span> (!check.IsValid(person, find))
            {
                valid = <span class="kwrd">false</span>;
                <span class="kwrd">break</span>;
            }
        }

        <span class="rem">//All tests passed - add to result set</span>
        <span class="kwrd">if</span> (valid)
            resultList.Add(person);
    }

    <span class="kwrd">return</span> resultList;
}&emsp;


Loop Comparison

With both the original and the strategy loop implemented, let’s compare their implementation.


Original Loop

<span class="rem">//For each person</span>
<span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
{
    <span class="rem">//Name Check</span>
    <span class="kwrd">if</span> ((find.FirstName != <span class="kwrd">null</span> && person.FirstName != find.FirstName) 
        || (find.LastName != <span class="kwrd">null</span> && person.LastName != find.LastName))
        <span class="kwrd">continue</span>;

    <span class="rem">//Age Check</span>
    <span class="kwrd">if</span> (find.MinimumAge != <span class="kwrd">null</span> || find.MaximumAge != <span class="kwrd">null</span>)
    {
        <span class="rem">//Calculate Age</span>
        <span class="kwrd">int</span> age = (DateTime.Today - person.DateOfBirth).Days / 365;

        <span class="rem">//Check Age Bands</span>
        <span class="kwrd">if</span> ((find.MinimumAge != <span class="kwrd">null</span> && age < find.MinimumAge) ||
            (find.MaximumAge != <span class="kwrd">null</span> && age >


Strategy Loop

<span class="rem">//For each person</span>
<span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
{
    <span class="kwrd">bool</span> valid = <span class="kwrd">true</span>;

    <span class="rem">//Perform All Checks</span>
    <span class="kwrd">foreach</span> (IPersonCheck check <span class="kwrd">in</span> checks)
    {
        <span class="rem">//Handle if fail</span>
        <span class="kwrd">if</span> (!check.IsValid(person, find))
        {
            valid = <span class="kwrd">false</span>;
            <span class="kwrd">break</span>;
        }
    }

    <span class="rem">//All tests passed - add to result set</span>
    <span class="kwrd">if</span> (valid)
        resultList.Add(person);
}


Benefits of the Strategy Pattern

As you can see, the second implementation of the loop is much more compact, as the code base grows and new checks are added they will not clutter the boiler plate logic. It offers a clean view of what the function is supposed to do, while the individual checks, no matter how complicated, are isolated and can be handled on a case by case basis. The benefits are summarised below.

  • Better modularity, smaller functions, easier to understand outside the boilerplate logic of the main loop.
  • Each condition is testable independently of all the others.
  • The loop will remain the same size, regardless of the number of checks added.
  • Can tailor the checks used, depending on scenario.
  • Can easily add tracing to diagnose rule failure in production systems.

 

Examples in .Net

The following classes are examples of the strategy pattern in the .Net libraries.

  • IComparer - Defines a strategy for comparing objects, which can be used for list sorting.
  • ICollection – Defines a collection which can be used for storing, managing and enumerating objects using a variety of algorithms, i.e. List, Queue, Stack, LinkedList, etc.

 

Composite Pattern

The composite pattern is a design pattern that enables a group of related objects to be treated as a single instance. This is achieved by the following

  • Define a family of classes.
  • Create an implementation that can contain multiple instances of the class.

 

Implementing the Composite Pattern

Using these steps, we can take the interface we created above for the strategy pattern, create a new implementation, and put the checks that we use into a single instance.

 

CompositePattern


Implementation Code

This is our implementation of a composite check, that bundles multiple checks together into a single instance.

<span class="kwrd">class</span> CompositePersonCheck : IPersonCheck
{
    <span class="kwrd">private</span> List<IPersonCheck> _checks;

    <span class="kwrd">public</span> CompositePersonCheck()
    {
        _checks = <span class="kwrd">new</span> List<IPersonCheck>();

        _checks.Add(<span class="kwrd">new</span> NameCheck());
        _checks.Add(<span class="kwrd">new</span> GenderCheck());
        _checks.Add(<span class="kwrd">new</span> AgeCheck());
        _checks.Add(<span class="kwrd">new</span> OccupationCheck());
        _checks.Add(<span class="kwrd">new</span> SalaryCheck());
    }

    <span class="preproc">#region</span> IPersonCheck Members

    <span class="kwrd">public</span> <span class="kwrd">string</span> Name
    {
        get { <span class="kwrd">return</span> <span class="str">"Composite Check"</span>; }
    }

    <span class="kwrd">public</span> <span class="kwrd">bool</span> IsValid(Person person, PersonFind find)
    {
        <span class="rem">//Perform All Checks</span>
        <span class="kwrd">foreach</span> (IPersonCheck check <span class="kwrd">in</span> _checks)
        {
            <span class="rem">//Add to removal list if not valid</span>
            <span class="kwrd">if</span> (!check.IsValid(person, find))
                <span class="kwrd">return</span> <span class="kwrd">false</span>;
        }

        <span class="kwrd">return</span> <span class="kwrd">true</span>;
    }

    <span class="preproc">#endregion</span>
}&emsp;


Calling Code

<span class="kwrd">public</span> List<Person> FindPeople(PersonFind find)
{
    List<Person> people = PersonCache.GetPeople();
    List<Person> resultList = <span class="kwrd">new</span> List<Person>();

    IPersonCheck check = GetCheck();

    <span class="rem">//Test each person against the composite check.</span>
    <span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
    {
        <span class="kwrd">if</span> (check.IsValid(person, find))
            resultList.Add(person);
    }

    <span class="kwrd">return</span> resultList;
}


Loop Comparison

With the third loop implemented, let’s look at how the different implementations stack up.


Original Loop

<span class="rem">//For each person</span>
<span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
{
    <span class="rem">//Name Check</span>
    <span class="kwrd">if</span> ((find.FirstName != <span class="kwrd">null</span> && person.FirstName != find.FirstName) 
        || (find.LastName != <span class="kwrd">null</span> && person.LastName != find.LastName))
        <span class="kwrd">continue</span>;

    <span class="rem">//Age Check</span>
    <span class="kwrd">if</span> (find.MinimumAge != <span class="kwrd">null</span> || find.MaximumAge != <span class="kwrd">null</span>)
    {
        <span class="rem">//Calculate Age</span>
        <span class="kwrd">int</span> age = (DateTime.Today - person.DateOfBirth).Days / 365;

        <span class="rem">//Check Age Bands</span>
        <span class="kwrd">if</span> ((find.MinimumAge != <span class="kwrd">null</span> && age < find.MinimumAge) ||
            (find.MaximumAge != <span class="kwrd">null</span> && age >


Strategy Loop

<span class="rem">//For each person</span>
<span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
{
    <span class="kwrd">bool</span> valid = <span class="kwrd">true</span>;

    <span class="rem">//Perform All Checks</span>
    <span class="kwrd">foreach</span> (IPersonCheck check <span class="kwrd">in</span> checks)
    {
        <span class="rem">//Handle if fail</span>
        <span class="kwrd">if</span> (!check.IsValid(person, find))
        {
            valid = <span class="kwrd">false</span>;
            <span class="kwrd">break</span>;
        }
    }

    <span class="rem">//All tests passed - add to result set</span>
    <span class="kwrd">if</span> (valid)
        resultList.Add(person);
}


Composite Loop

<span class="rem">//For each person</span>
<span class="kwrd">foreach</span> (Person person <span class="kwrd">in</span> people)
{
    <span class="kwrd">if</span> (check.IsValid(person, find))
        resultList.Add(person);
}


Benefits of the composite pattern

The benefits of this pattern are not as obvious as the initial strategy implementation for this particular scenario; it is just one step further in shaping the code and reducing the boiler plate loop. However, it puts us in a situation where we have a massive amount of flexibility as to how we apply our conditions and lays the groundwork for a very powerful rule system. The benefits are summarised below:

  • Easy to create sets of checks / rules that are bundled together.
  • Easy to create composite conditional checks such as AND / OR.
  • Easy to create check / rule trees, composed of sets, and conditionals.


Examples in .Net

The following classes are examples of the composite pattern in the .Net libraries.

  • Control – Winforms component that can be composed of multiple controls, handling them as a single instance.
  • XmlElement – Xml element that can contain one or more XmlElements, to build up the DOM tree.


Note

Generally, a “pure” implementation of the composite pattern will have a concrete base class and use inheritance, rather than an interface. However, for this example I decided that it was too heavy weight when an interface handled it just as well, and it achieves the same results.

License

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

About the Author

Tristan Rhodes
Software Developer (Senior)
United Kingdom United Kingdom
My name is Tristan Rhodes, i wrote my first Hello World program in 2001 and fell in love with software development (I actualy wanted to be a Vet at the time).
 
I enjoy working with code and design patterns.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 20 Oct 2010
Article Copyright 2010 by Tristan Rhodes
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid