Design patterns seem to be a controversial topic. On one hand, many developers seem to love them and treat the famous book by the Gang of Four like sacred scripture. On the other hand, many developers loath the very idea of design patterns. It’s not too hard to find posts around the web with titles such as “Design Patterns are Bad Design,” “Following Design Patterns Is A Bad Idea,” and of course, the inevitable “Design Patterns Considered Harmful.”
What can we take from that? Well, as it is with pretty much everything in our industry, it’s really hard to reach some form of consensus on design patterns. My take is this: design patterns are tools. Some are more useful, others less useful. Some may be outright harmful. And others may not make sense for your scenario, but maybe for Joe who sits three desks from you, they’re a gift from heaven.
But that’s not what this post is really about (i.e. deciding whether design patterns are “good” or “bad”). As we’ve seen, such discussion is simplistic and misses the point.
What we’re going to do instead is cover three design patterns that haven’t aged quite well. These design patterns are ones that you no longer go around implementing. And if you do, you probably shouldn’t.
Let’s get started.
Specification
The first item in our list is the Specification Pattern.
What Is It?
What is this pattern about? As its name suggests, the specification pattern is a way of encapsulating domain rules in objects. After their creation, developers can test if the various domain objects are satisfied by the specifications.
The specifications can also be chained. That allows developers to test objects for compliance with any, or all, of the specifications in a list.
Why Did It Age Poorly?
It’s possible that you’ve read some or many posts singing the praises of the specification pattern during your career. You might have read and analyzed several sample implementations of this pattern on GitHub. Heck, it’s not terribly unlikely you’ve implemented it yourself one or several times in your career!
If one or more of my assertions above ring true, you might be wondering what my problem is with the poor old specification pattern. The reasons are many, but if I were to summarize them into one sentence, it would be this: the specification pattern is a glorified “if” statement.
I feel that this pattern just doesn’t do a lot to justify its existence. Take a look at the following example, which is a code sample taken from the Wikipedia article on the pattern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
public interface ISpecification<T> { bool IsSatisfiedBy(T candidate); ISpecification<T> And(ISpecification<T> other); ISpecification<T> AndNot(ISpecification<T> other); ISpecification<T> Or(ISpecification<T> other); ISpecification<T> OrNot(ISpecification<T> other); ISpecification<T> Not(); } public abstract class LinqSpecification<T> : CompositeSpecification<T> { public abstract Expression<Func<T, bool>> AsExpression(); public override bool IsSatisfiedBy(T candidate) => AsExpression().Compile()(candidate); } public abstract class CompositeSpecification<T> : ISpecification<T> { public abstract bool IsSatisfiedBy(T candidate); public ISpecification<T> And(ISpecification<T> other) => new AndSpecification<T>(this, other); public ISpecification<T> AndNot(ISpecification<T> other) => new AndNotSpecification<T>(this, other); public ISpecification<T> Or(ISpecification<T> other) => new OrSpecification<T>(this, other); public ISpecification<T> OrNot(ISpecification<T> other) => new OrNotSpecification<T>(this, other); public ISpecification<T> Not() => new NotSpecification<T>(this); } public class AndSpecification<T> : CompositeSpecification<T> { ISpecification<T> left; ISpecification<T> right; public AndSpecification(ISpecification<T> left, ISpecification<T> right) { this.left = left; this.right = right; } public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate); } public class AndNotSpecification<T> : CompositeSpecification<T> { ISpecification<T> left; ISpecification<T> right; public AndNotSpecification(ISpecification<T> left, ISpecification<T> right) { this.left = left; this.right = right; } public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate) != true; } public class OrSpecification<T> : CompositeSpecification<T> { ISpecification<T> left; ISpecification<T> right; public OrSpecification(ISpecification<T> left, ISpecification<T> right) { this.left = left; this.right = right; } public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate); } public class OrNotSpecification<T> : CompositeSpecification<T> { ISpecification<T> left; ISpecification<T> right; public OrNotSpecification(ISpecification<T> left, ISpecification<T> right) { this.left = left; this.right = right; } public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate) != true; } public class NotSpecification<T> : CompositeSpecification<T> { ISpecification<T> other; public NotSpecification(ISpecification<T> other) => this.other = other; public override bool IsSatisfiedBy(T candidate) => !other.IsSatisfiedBy(candidate); } |
One interface, two abstract classes, and five non-abstract classes. That’s a whole lot of code. To be exact, 76 lines, excluding the blank ones. If you add things like XML comments and unit tests, the figure will grow even higher. Notice that those classes are only the “framework” if you will. You’d still have to create a number of classes for the actual specifications that reflect the rules of your domain.
All of that work for what? In my view, it pretty much amounts to reinventing conditional logic, but in a way more verbose and clunky manner.
What to Do Instead?
It depends. If the condition is simple enough, and it feels like it belongs to the domain object itself, then put it there. Should it be a method or a property? There are guidelines for that.
But there are situations in which, for some reason, you can’t or won’t do that. That’s exactly why the specification pattern exists in the first place, after all. What do you do instead?
In my view, the answer is a feature that has been around for quite a long time: extension methods. Yes, I know that extension methods can be problematic. But that is true of pretty much every tool, framework, or language feature. And how do you go about that?
Just add a new extension method for each specification that you need, preferentially in a different layer/assembly from where your domain objects reside. That way, your domain objects don’t get cluttered with a lot of methods that aren’t relevant to the domain. Also, the specification methods will only show up in Intelisense to the developers that need them and express that by using the relevant namespace.
Let’s revisit the sample from the Wikipedia article. The article goes on to show a usage example of the classes created before.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var OverDue = new OverDueSpecification(); var NoticeSent = new NoticeSentSpecification(); var InCollection = new InCollectionSpecification(); // example of specification pattern logic chaining var SendToCollection = OverDue.And(NoticeSent).And(InCollection.Not()); var InvoiceCollection = Service.GetInvoices(); foreach (var currentInvoice in InvoiceCollection) { if (SendToCollection.IsSatisfiedBy(currentInvoice)) { currentInvoice.SendToCollection(); } } |
How would that code look like using extension methods as specifications? Let’s check it out.
1 2 3 4 5 6 7 8 9 10 |
var invoices = Service.GetInvoices(); foreach (var currentInvoice in invoices) { var shouldSendToCollection = currentInvoice.OverDue() && currentInvoice.NoticeSent && !someCollection.Contains(currentInvoice); if (shouldSendToCollection) currentInvoice.SendToCollection(); } |
I’ve taken the liberty of changing the name of the variable that gets assigned the return of Service.GetInvoices(). It was originally “InvoiceCollection,” but I don’t like that since it makes the code a little bit confusing (“if it’s already in the collection, why are we trying to send it to the collection?”). By calling it “invoices,” I make it very clear that there are two distinct collections we’re talking about. And as a minor offense, “InvoiceCollection” also goes against most C# naming conventions. It’s a local variable, so use camelCase.
I probably can guess what you’re thinking. Why stop there? And you’re right. There’s nothing stopping us from taking it one step further and creating a new extension method called “ShouldSendToCollection()” that does all of that in one take. If this verification occurs in many places in the code, then I’d say it’s the right direction to go.
Iterator
The second on our list is the iterator design pattern.
What Is It?
What’s this pattern all about? Let’s see Wikipedia’s definition:
In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container’s elements.
I think that definition is good enough. Think of the extremely common situation of iterating through the items in a list in order to perform some filtering. If the type provides an indexer, then you can use a “for” loop. That’s not always the case, though. And in those kinds of situations, the iterator pattern can prove handy.
Why Did It Age Poorly?
I’d say this is a somewhat happier version of the previous case. The iterator pattern was, in many ways, a victim of its own success. As a C#/.Net developer, you’d be hard-pressed to spend more than two days in a row without using it. You just don’t go around implementing it.
You use this pattern every time you use a foreach loop. Let’s see a bit of code.
1 2 3 4 5 6 |
IEnumerable<int> numbers = GetABunchOfNumbers(); foreach (var number in numbers) { // do stuff } |
I really like the foreach construct. It’s very concise and self-documenting. It reads like natural language. But it’s a lie. Or, to put it another way, syntactic sugar. Here’s the truth.
1 2 3 4 5 6 7 8 |
IEnumerable<int> numbers = GetABunchOfNumbers(); IEnumerator<int> enumerator = numbers.GetEnumerator(); while (enumerator.MoveNext()) { int number = enumerator.Current; // do stuff with number } |
What’s that “IEnumerator” thing, you may ask? Well, that’s nothing more and nothing less than a good old Iterator, but it’s under a new name.
What to Do Instead?
The answer here is easy. Just keep doing what you already do, like using the foreach loop. You just don’t need to go and implement the iterator pattern. Don’t reinvent the wheel—unless you want to learn about wheels, of course.
Command
The third on our list is the command design pattern.
What Is It?
Sometimes, a piece of code knows that something has to be done, but for whatever reason can’t do it. On the other hand, there’s another piece of code somewhere that has the resources and knowledge to execute, but it doesn’t know what needs to be done. How do we bridge that gap? We do so by representing said operations as objects that can be sent from place A to place B. And that’s what the command pattern is all about.
In its classic form, the command pattern is amazingly simple.
1 2 3 4 |
interface ICommand { void Run(); } |
Sure, there can be many variations. But in its basic form, that’s pretty much it. By creating implementations of this interface, developers can define as many commands as they need. Then it becomes possible to decouple the definition of the command from its execution.
1 2 3 4 5 6 7 |
public void RunAllCommands(IEnumerable<ICommand> commands) { foreach (var command in commands) { command.Run(); } } |
Why Did It Age Poorly?
In short, OO languages have been getting more and more functional features, and C# is no exception. Delegates were there from the beginning. C# 2.0 introduced anonymous methods and the next version introduced lambda expressions. Together, these features make for a very powerful and concise way to solve the problem of sending an action for another area of the code to perform.
What to Do Instead?
As in the previous pattern, the answer is “nothing.” As in, “just keep doing what you’ve probably been doing and things will be just fine.” Good old Action and Func can take care of most of your needs.
Some Design Patterns May be Like the Wine…but Not All of Them
In this post, we’ve covered three design patterns that didn’t age well. The chosen items may have surprised you, but you have to understand my definition of “aging poorly.” I don’t intend to mean that these specific patterns are bad/evil/sins/another simplistic label and that you shouldn’t never, ever, use them again.
For instance, when implementing clean architecture, the use cases are essentially the command pattern. You may also find yourself in the not so great situation of having to deal with an ancient legacy application, written with very old versions of the C#/.Net, lacking niceties such as extension methods. And for nonsense business reasons, they won’t allow you to update. In that case, you wouldn’t have a choice, except implementing the specification pattern, for instance.
The reasons why the aforementioned posts haven’t aged well are below:
- One of them was cumbersome and probably too heavy-weight from the beginning.
- One became sort of irrelevant when the designers added more functional niceties to the language/framework.
- The other was so successful that it disappeared into the very fabric of our languages and frameworks.
Not that bad a way to go, if you ask me.
I don’t think I’d say Iterator or Command have “aged poorly”, as much as they are now “solved problems” — you don’t need to implement them because someone has done the hard work for you already. In fact, I’d say that the Iterator is now more relevant than ever, as “yield return” makes implement the pattern yourself quite easy.
Iterators are far better than foreach().
Nearly all implementations of foreach() will not allow you to ESCAPE until the loop finishes; this is why iterators are used.
There is one big advantage to the specification pattern that is not to be forgotten. You can replace a if statement with a class name. The next question is, what is faster to comprehend when you read code, an if statement or an explicitly defined class name ?
if( condition1 and condition 2 or condition 4) vs new ExplicitConditionSpecification.IsSatisfied().
This outweights your implied problem that you have to create a couple more classes to make this work imo.
And there is the benefit that you can reuse your specifications.
if you want to exit a loop you probably dont need foreach but “first” higher order function