Are Design Patterns Really Anti-Patterns?

When I first read the Gang of Four's monumental book Design Patterns: Elements of Reusable Object-Oriented Software, I thought that I had discovered the Holy Grail of software development. I was amazed at the power and flexibility that design patterns afforded me in solving difficult problems, the seeming elegance bestowed upon the humble programmer who was constantly embroiled in battle against the demons of ever widening problem-space complexity.

As so often happens to those who seek continuous improvement, however, very large tectonic shifts in my worldview started occurring as I learned more and more about the theory behind software and started opening myself to different paradigms of programming.

Buzzword Bingo

As more and more recruiters identified Design Patterns as a "hot item" on my resume I got an increasingly uneasy feeling, not because it's a frivolous skill to have—design patterns are useful in a number of scenarios—but because people I talked to didn't understand why they were useful. Moreover, I find that a lot of programmers who should know better see them as a silver bullet instead of a set of tools that are appropriate in specific contexts.

Failure is Always an Option

Not only is failure always an option, it is necessary for growth. One of my big pet projects that I've worked on/off again the past few years is a stock market analysis engine called Continuum. My intent behind the last iteration was to create a genetic algorithm running behind a back testing engine. The genetic algorithm would iterate against a number of historical data sets and over time evolve an effective trading strategy consisting of technical and fundamental indicators. Simple, right? Nope.

Even with all my knowledge of loose coupling, abstraction, and other architectural best practices I found that the solution to this problem was still very hard for me to conceptualize. After playing with a bunch of reflection-based approaches and something I dubbed a "meta-interface," my project slammed into a brick wall. I put it on hold and pursued other endeavors. Little did I know at the time, there in fact is a tool which lends itself very well to this problem (partial function applications) but it is not available in the world of object-oriented programming.

OOP is Fundamentally Broken

After my setback with Continuum I started to wonder if I wasn't barking up the wrong tree entirely using an object-oriented approach. In addition, I witnessed a number of programmers in the "professional" workplace nonchalantly employing horrific OOP practices such as:

  • Using inheritance to extend the behavior of a class/component.
  • Creating "God" classes containing hundreds of properties and/or methods.
  • Neglecting the power of OOP entirely and just using C# to write really bad procedural code.

People smarter than I have pointed out that object-oriented languages likely weren't even implemented correctly in the first place! As it all sunk in, I came to realize that:

  1. There is a better way.
  2. It's conceivable that OOP design patterns, especially those of the behavioral variety, are hacks which attempt to compensate for fundamental shortcomings of the OOP paradigm.

Around this time I also realized that more than half the C# code I was writing consisted of LINQ query expressions and lambdas.

I was ready for the next step...

Functional Programming in the .NET World

I began teaching myself F#, Microsoft's "official" functional programming language for the .NET stack, and immediately fell in love.

The declarative style of the language jived well with my style of thinking and made it seem almost effortless for me to implement complex abstractions and algorithms.

Philosophically, I think it's fun to imagine that the universe around us is really built from processes instead of things. For instance, a person isn't a concrete object with properties and behaviors but is rather a process of life that changes and adapts over time. From this perspective, functional programming paradoxically lends a more accurate representation of the real world than OOP.

More pragmatically, functional programming languages are far better suited to representing complex algorithms because they facilitate behavioral composition as opposed to state composition (OOP).

The Proof is in the Pudding

In the next few blog entries I will set out to show how a functional programming language, F#, may be used to implement many of the classic Gang of Four design patterns in a functional, declarative, and (hopefully) much more elegant style. I will begin with some of the low-hanging fruit.

Let's see what the Chain of Responsibility pattern might look like in a functional style as opposed to a standard C#, object-oriented implementation...

Next up: A functional version of the Chain of Responsibility

Think Multidimensionally

Many people try their hand at computer programming; most do it wrong. The fact of the matter is that it takes a different kind of thinking, a higher-dimensional form of thinking, to master the art and science of software development. [1]

Like the humble square in Edwin Abbott's famous story Flatland many programmers are trapped in a one-dimensional approach to problem solving without even an inclination that there's a better way.

To build software solutions that are simple yet elegant, extensible and reliable, it takes a three-dimensional mode of thinking which few possess, or even realize exist. Those three dimensions are:

Procedural

Inheritance

Compositional

Moving in a Straight Line: Procedural Programming

I’m sure you wrote your first “Hello World” program in a simple, procedural language such as BASIC. Procedural programming is much like baking a cake: you have a starting point, you have an objective, and you have a series of steps which are executed in linear fashion in order to get from the starting point to the final objective.

Of course, procedural programming may involve various flow control mechanisms such as conditional (IF/THEN) statements, loops, function and/or procedure calls, and more. These do not fundamentally add depth to this form of programming—they all operate along a single continuum of machine logic.

Here is an example of procedural programming in C#:

static void Main(string[] args)
{
    int count = 0;
    Console.WriteLine("Starting...");
 
Loopstart:
 
    Console.WriteLine(count * count);
    count++;
    if (count < 10) goto Loopstart;
    Console.WriteLine("Done.");
    Console.ReadLine();
}

[2]

Anyone who calls him/herself a programmer understands this dimension. The next one is a little bit trickier...

Ascension and Descension: Inheritance

[3]

Inheritance is the notion that one class of entities derives from a more general class. For example, a “Person” class might derive from an “Animal” class, which in turn derives from an “Organism” class. Each child class inherits the behavior and properties of its parent classes.

This simple diagram illustrates the relationships amongst these classes.

In C# the classes might be coded like this:

abstract class Organism
{
    public abstract string Genus { get; }
    public abstract string Species { get; }
}
 
class Animal : Organism
{
    public void Walk() { }
}
 
sealed class Person : Animal
{
    public void Talk() { }
 
    public string Name { get; set; }
}

Inheritance is a powerful, yet often abused feature of modern object-oriented languages. Examples of inheritance abuse are legion. A simple glance at this thread will show you how far down this rabbit hole goes.

Probably about half to three quarters of the programmers out there understand inheritance and how to apply it to trivial problems. However, when approaching much more difficult problems most of them fly off the track because they try to use inheritance to extend properties and behavior of parent classes. This is WRONG WRONG WRONG.

Inheritance has two proper, virtuous uses:

  1. Polymorphism—the substitution of one entity by another, similar entity.

  2. Code reuse amongst classes which are conceptually equivalent at a base level.

One must ask him/herself: “Are all of these classes logically equivalent at a base level? Can I substitute a child class for a parent class and still expect it to work as intended?”

This is called the Liskov Substitution Principle and it is one of the most important concepts of OOP.

Now that we have an understanding of inheritance and its purpose, let’s move on to the last and most important dimension...

Dissolve and Coagulate: Object Composition

[4]

After reading my philosophy concerning inheritance you may be wondering just how you might go about extending the properties and behaviors of a base class, or how you might build intricate, many-faceted data structures which serve different purposes at runtime. Object composition is the most obvious choice. [5]

Object composition is the notion that an object instance of a certain class may be composed of smaller, more basic pieces which are generally interchangeable. Some design patterns, such as Builder, operate directly along this dimension.

Composition may be visualized like this:

Here is an example in C# of proper use of composition:

abstract class HumanBehavior
{
    public abstract void ExecuteBehavior(Person person);
}
 
sealed class Jump : HumanBehavior
{
    public override void ExecuteBehavior(Person person)
    {
        Console.WriteLine("{0} is jumping up and down.");
    }
}
 
class Person
{
    public HumanBehavior Behavior { get; private set; }
 
    public string Name { get; private set; }
 
    public Person(string name, HumanBehavior behavior)
    {
        Name = name;
        Behavior = behavior;
    }
 
    public void DoSomething()
    {
        Behavior.ExecuteBehavior(this);
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        Person john = new Person("John", new Jump());
        john.DoSomething();
        Console.ReadLine();
    }
}

This trivial example merely prints the text “John is jumping up and down.” What’s important to get out of it is the notion that behaviors which would otherwise get slapped on to a class using inheritance have instead been abstracted out into their own family of classes. The Person class now becomes a composite which may accept any child class of HumanBehavior and call it as it sees fit.

It is clear that the dimensions of Object Composition and Inheritance go hand in hand and flow easily into and out of each other. This is not an accident.

  • Inheritance facilitates composition through polymorphism. It also prevents abuse of Composition (objects built from too finely-grained pieces, a.k.a. compositional chimeras) and flat-out cut and pasting by allowing for code reuse at a procedural (first dimension) level.

  • Composition bolsters inheritance by removing extension-oriented code from the class itself, and allowing the class to focus on only the most essential elements—the quintessence, as it were. [6]

Composition and inheritance are both powerful forces to be reckoned with when used in unison. However, composition trumps inheritance. As the Gang of Four state plainly in their monumental book Design Patterns: “Favor object composition over class inheritance.”

By using composition to extend functionality you create software that is more flexible, maintainable, extensible, and powerful.

A note on explicit interfaces:

The use of inheritance (abstract classes, etc) is obviously not the only means of implementing a polymorphic design. Languages such as C# and Java support explicit interface declarations which function as de-facto code contracts. I’m all for using interfaces polymorphically to facilitate object composition, but beware... this is another language feature that is often abused.

Newbie and intermediate level programmers will often use interfaces as their tool-of-choice for implementing polymorphism. This is a mistake. The designers of both Java and C# chose to build in support for interfaces because it represents a compromise in languages which otherwise do not support multiple inheritance. They wisely chose to avoid the myriad headaches which come with the multiple inheritance model; for any given class, programmers are limited to a single “silver-bullet” parent class which they can derive from alongside any number of interfaces for “mix-in” behavior.

C++ programmers might not be fond of this model but I love it. Why? It enforces the Single Responsibility Principle. One class—one purpose. End of story. Too often have I seen programmers who should’ve known better implement polymorphic chimeras by overloading a class with too many interfaces. Don’t go down that road.

Like Yin and Yang, inheritance and composition flow elegantly from one to the other. Interfaces support both of those dimensions to achieve a greater harmony.

Summary

The three dimensions of software development—procedural, inheritance, compositional—form the basis of a powerful and elegant approach to problem solving.

Almost every programmer understands the first dimension. A few understand the second. A handful understand the third.

Programmers who understand all three dimensions and how to apply them properly are rare as unicorns.

Modern software development truly is a form of alchemy, both an art and a science which reacts and interacts to create a Great Work, a machine built of pure mind-stuff. To master this craft requires thinking in three dimensions...

Right?

Nope. There’s more.

Prepare yourself. You’re about to get sucked through the Klein bottle into the 4th dimension...

Evolution and Devolution: Time

There’s another dimension to software development which often gets overlooked: time. Of course none of us have a crystal ball. Nevertheless, it is imperative that a programmer visualizes possible future scenarios and considers how a system will change over time. This is tricky for even the most adept programmer because it involves thinking about the three basic dimensions and then projecting those onto a potentially infinite number of future scenarios.

Some example considerations involving the fourth dimension may be:

  • How often will a client need to extend a base class/component?

  • Are the existing building blocks sufficient to allow for change six months, a year, five years into the future?

  • What are the resource impacts of a given design/feature? Will that feature scale over time as the system changes?

  • What extensibility points should be put in place in anticipation of future enhancements or new technologies which don’t exist yet?

  • Which components are NOT extensible?

  • Which components or subsystems will likely get scrapped at some point?

  • Where is the market trending? What will the new hot technologies be tomorrow?

  • How to keep the system open and extensible, yet concrete enough that it does what it needs to do?

These considerations and more all apply to any software endeavor, especially on a larger scale.

Final Words

What is important at the end of the day isn’t necessarily which cool design pattern you implemented, or some awesome new framework that looks good on a resume. It’s having an understanding of the tools that you have at your disposal and using the most appropriate ones to solve a given problem. That’s it.

Object-oriented languages are exceptionally powerful tools at a core level because they allow for a type of multidimensional problem conceptualization which neatly mirrors real life entities. Unfortunately, many programmers don’t understand the dimensions involved, how they interact, or when each is appropriate in a given context. As we proceed into the 21st century even more interesting paradigms will emerge which make OOP look quaint, and terms like multidimensional and non-linear will become as commonplace as the mouse and keyboard. But before we take that next step let’s look around at the tools we have, and thank our lucky stars that we’re not still coding in FORTRAN.

- John

Notes:

1 This is in reference to the object-oriented paradigm of software development and to the C# language in particular. Other languages such as F# involve fundamentally different paradigms, such as functional programming.

2 This example is for illustrative purposes only. The use of the “goto” keyword is NEVER recommended in C# for obvious reasons.

3 The astute learner will have already noticed that a number of abstract programming concepts have an amazing similarity to ideas stemming from the lost art and science of alchemy. In this case, the Inheritance dimension is equivalent to the alchemical notion of “As above, so below.” That is, the macrocosm is contained within the microcosm.

4 Latin: “Solve et coagula”—another alchemical concept. This is the notion that a greater whole may be broken down into constituent parts and then reconstituted into some new form.

5 There is also the dark art of Reflection, but I will not mention it further here.

6 Here is another notion from alchemy. “Quintessence” is generally used to refer to the deepest, most essential presence of something, that which gives it its defining characteristics.

Subscribe to