I went through a phase earlier in my career where I thought design patterns were the be-all, end-all of software design. Any system which I needed to design started with the applicable patterns: Factories, Repositories, Singletons, you name it. Invariably, though, these systems were difficult to maintain and more difficult to explain to my coworkers, and I never quite seemed to put two and two together. The fact was that I just didn't understand them the way I thought I did.

Five years later, I've now been researching these same design patterns for a presentation I'm giving at my day job, the goal of which is to demonstrate how said patterns help us maintain our projects in a C#/.NET environment. I've built and documented several examples for Adapter, Facade, Abstract Factory, etc. They are making sense to me, I'm definitely understand them more thoroughly, but there's something still seems a little... off.

To be clear, I've never read the Gang of Four book these patterns are defined in, so it's possible there's reasoning in the book that would alleviate my concerns. In fact, all of my knowledge about these patterns has come from online resources such as Do Factory. And yet the more I understand them, the more I believe that design patterns are not goals which we should strive for.

Take the Adapter pattern as an example. Adapter strives to provide an abstraction over an interface such that a client which expects a different interface can still access the old one. Imagine that we have a legacy API which looks something like this (taken directly from Do Factory above):

class Compound
{
    protected string _chemical;
    protected float _boilingPoint;
    protected float _meltingPoint;
    protected double _molecularWeight;
    protected string _molecularFormula;

    // Constructor
    public Compound(string chemical)
    {
        this._chemical = chemical;
    }

    public virtual void Display()
    {
        Console.WriteLine("\nCompound: {0} ------ ", _chemical);
    }
}

class ChemicalDatabank
{
    // The databank 'legacy API'
    public float GetCriticalPoint(string compound, string point)
    {
        // Melting Point
        if (point == "M")
        {
            switch (compound.ToLower())
            {
                case "water": return 0.0f;
                case "benzene": return 5.5f;
                case "ethanol": return -114.1f;
                default: return 0f;
            }
        }
        // Boiling Point
        else
        {
            switch (compound.ToLower())
            {
                case "water": return 100.0f;
                case "benzene": return 80.1f;
                case "ethanol": return 78.3f;
                default: return 0f;
            }
        }
    }

    public string GetMolecularStructure(string compound)
    {
        switch (compound.ToLower())
        {
            case "water": return "H20";
            case "benzene": return "C6H6";
            case "ethanol": return "C2H5OH";
            default: return "";
        }
    }

    public double GetMolecularWeight(string compound)
    {
        switch (compound.ToLower())
        {
            case "water": return 18.015;
            case "benzene": return 78.1134;
            case "ethanol": return 46.0688;
            default: return 0d;
        }
    }
}

However, our new system expects Critical Point, Molecular Weight and Molecular Structure to be properties of an object called RichCompound, rather than queries to an API, so the Adapter patterns says we should do this:

class RichCompound : Compound
{
    private ChemicalDatabank _bank;

    // Constructor
    public RichCompound(string name)
        : base(name)
    {
    }

    public override void Display()
    {
        // The Adaptee
        _bank = new ChemicalDatabank();

        _boilingPoint = _bank.GetCriticalPoint(_chemical, "B");
        _meltingPoint = _bank.GetCriticalPoint(_chemical, "M");
        _molecularWeight = _bank.GetMolecularWeight(_chemical);
        _molecularFormula = _bank.GetMolecularStructure(_chemical);

        base.Display();
        Console.WriteLine(" Formula: {0}", _molecularFormula);
        Console.WriteLine(" Weight : {0}", _molecularWeight);
        Console.WriteLine(" Melting Pt: {0}", _meltingPoint);
        Console.WriteLine(" Boiling Pt: {0}", _boilingPoint);
    }
}

The RichCompound class is therefore an adapter for the legacy API: it converts what was service calls into properties. That way our new system can use the class it expects and the legacy API doesn't need to go through a rewrite.

Here's the problem I have with design patterns like these: they seem to be something that should occur organically rather than intentionally. We shouldn't directly target having any of these patterns in our code, but we should know what they are so that if we accidentally create one, we can better describe it to others.

In other words, if you ever find yourself thinking, "I know, I'll use a design pattern" before writing any code, you're doing it wrong.

What patterns don't help with is the initial design of a system. In this phase, the only thing you should be worried about is how to faithfully and correctly implement the business rules and procedures. Following that, you can create a "correct" architecture, for whatever correct means to you, your business, your clients, and your code standards. Patterns don't help during this phase because they artificially restrict what you think your code can do. If you start seeing Adapters everywhere, it becomes much harder to think of a structure that may not have a name but would fit better in your app's architecture.

I see design patterns as tools for refactoring and communication. By learning what they are and what they are used for, we can more quickly refactor troublesome projects and more thoroughly understand unfamiliar ones. If we see something that we can recognize as, say, the Composite pattern, we can then look for the individual pieces of the pattern (the tree structure, the leaves and branches, the common class between them) and more rapidly learn why this structure was used. Recognizing patterns help you uncover why the code was structured in a certain way.

Of course, this strategy can backfire if the patterns were applied inappropriately in the first place. If someone used a Singleton where it didn't make sense, we might be stuck, having assumed that they knew what they were doing. Hence, don't use patterns at when beginning your design of a project, use them after you've got a comprehensive structure in place, and only where they make sense. Shoehorning patterns into places where they don't make sense is a recipe for unmaintainable projects.

Software design patterns are tools, not goals. Use them as such, and only when you actually need them, not before.