Friday 17 December 2010

Yet Another Blog On Design Patterns


Oh no, not 'YABODP'.
My fiancée and I are both developers and she's only slightly less geeky than me (Sorry Jo...)
Design patterns are something that I've been interested in for a number of years, but I don't carry them around in my head like a seasoned practitioner does.  Okay, maybe I do have "Façade", "Factory", "Observer" or "Singleton" at my fingertips but, to be honest, I couldn't tell "Bridge" from "Strategy", without recourse to the GOF.
As it was getting towards Christmas and as I tend to amble into work at 8-00 I made it my challenge to spend no more than the first hour of the day learning a pattern, coding it and sending it to my beloved; a kind of Christmas Design Patterns Advent Calendar. The hope was that this would be to the benefit of us both.
What I ended up with was a set of patterns that had a little bit more opinion than you get on the rather brilliant "dofactory", but a lot less information than if you pick up the rather excellent 600 page head first design pattern book.  Jo said they were useful and sent them onto her team so I thought I'd share them here… if they help just one person understand one pattern a little bit more, then they've been worth sharing.
Before we get started
If you're new to these patterns then I'd suggest that the key concept you need to get your head around is one of the golden rules of software construction:
"Depend on abstraction, not implementations"
Most of the diagrams below feature a Client talking to a set of classes that comprise the participants in the pattern.  In each case the Client could be a class, component or entire system, no matter. What is important is that the dependency line that is drawn from the Client to the pattern lands on an abstraction, not an implementation.  In terms of code, then, the Client should reference an abstract class or interface. In terms of UML the line should go from the Client box to a box whose name is in italics.
The motivation behind and the benefit of this structure is that concrete implementations can be modified, replaced or created and as long as the implementation class honours the contract stipulated in the interface, the Client shouldn't care about these implementation changes.  In practical terms, the Client shouldn't even need to be recompiled.  Being blissfully unaware of the implementation details satisfies another related golden rule of software design, which is one of encapsulation.
/// <summary>
/// Here's the abstraction
/// </summary>
public interface IBuildingMaterial
{
    bool IsPostModernist { get;}
}
 
/// <summary>
/// Here's a concrete Concrete implementation!
/// </summary>
public class Concrete : IBuildingMaterial
{
    public bool IsPostModernist
    {
        get { return true;}   
    }
}
 
/// <summary>
/// Here's a concrete Stone implementation?
/// </summary>
public class Stone : IBuildingMaterial
{
    public bool IsPostModernist
    {
        get { return false; }
   }
}


In the code above IBuildingMaterial is the abstraction and Concrete and Stone are the (concrete, ahem) implementations.  Client code should only reference the abstraction. We should be able to substitute in Stone for Concrete without the Client collapsing.
static void ClientCode(IBuildingMaterial material)
{
   material.IsPostModernist;
}
But ultimately, of course, a real implementation class has to be created somewhere for the Client to work with, even though the Client only "knows" about the interface.  Abstract classes and interfaces can't be created, after all.  So the question is how do we insulate the Client from these implementations and reap the benefits of clients being dependent on abstractions only?
static void ClientCode()
{
  // As soon as we reference a Concrete class we are breaking
  // our "program to an abstraction, not an implementation rule"
  IBuildingMaterial material = new Concrete(); // We can't do this.
  bool
 isItUgly = material.IsPostModernist;
}
The answer is to give some other class (or container) the responsibility for creating an implementation class and handing out either an abstract class or an interface to it. Then an existing implementation class can be modified or replaced, or a new one introduced, without client code needing to be rebuilt.  We could change something about Concrete or Stone, or we could bring in a new building material, such as "Steel":

public class Steel : IBuildingMaterial
{
   public bool IsPostModernist
   {
      get { return true; }
   }
}

 
If that responsibility to create a new class and hand it out falls to a class, then that class is often called a "Factory". If that responsibility falls to a container, then that container is often called an "Inversion of Control" container or a "Dependency Injection" system.  Although IOC containers have other capabilities, for the purposes of understanding the benefit of design patterns, Factories and IOC containers are what I think of as "class dispensers".  They isolate the creation of concrete classes from client code and they then hand out abstractions of these classes to those clients.  Consider the example below – there is no mention of a concrete class here, but we can see that the BuildingSupplier class has a CreateMaterial method that returns an IBuildingMaterial abstraction.  Without knowing about the internals of the method, we can easily imagine that it must create a concrete implementation class and hand it back as an IBuildingMaterial abstraction.
static void ClientCode()
{
   IBuildingMaterial material = BuildingSupplier.CreateMaterial();
   bool isItUgly = material.IsPostModernist;   
}
(Just to further decrease the coupling, these class dispensers are usually capable of reading some configuration to resolve the name of the class to create, which they then create through reflection. This means that neither the client nor the class dispenser needs to reference the assembly containing the class.)
Let’s now look at an implementation of our BuildingSupplier (factory) class:

public class BuildingSupplier
{    
    /// <summary>
    /// Imagine that this value is held in a config file
    /// </summary>
    

    private static string _configClass =
       
"ClassDispenserExample.Concrete"
;
 
    
public static IBuildingMaterial
 CreateMaterial()
    { 
        
return Activator
.CreateInstance(
           
Type.GetType(_configClass)) as

           
IBuildingMaterial
;      
    }
}
So, whenever you look at a class diagram for a design pattern and you see a line going from a class called "Client" to an abstraction class participating in the pattern, think of the client talking to a concrete implementation that is created by a class dispenser which then hands out an interface or an abstract ancestor of that class.  Also think of the client as being physically separate from the concrete implementation classes that implement the abstraction.   Once you have that in place implementations can be changed or swapped for other implementations.  Also, once you have these principles lodged in your brain, understanding the patterns and what benefits they give you should be more apparent.

If my sister Clare ever ends up on this site (though I'm not sure why she would as she is a lawyer...) I can only apologise if some of the patterns suggest her two dogs (Dot and Royston) are intellectually or aesthetically inferior to mine (Jess)!

No comments:

Post a Comment