The Truth Behind Design Patterns

Design patterns help you write good software. How can you master all 23+ of them? You are probably better of focusing on the underlying truth: the design principles.

The book “Design Patterns: Elements of Reusable Object-Oriented Software” is a classic and a must-read (see post). When I first came across the book, I read about all those 23 fantastic patterns. They were all different and applicable in different situations. As a computer scientist, I hate special cases, so I wanted to see some kind underlying truth. At the time, I did not see a pattern (ironic, huh? :).

The book “Implementing Lean Software Development” (see post) makes the following definitions:

  • Principles are underlying truths that don’t change over time […]”
  • “[…] practices are the application of principles to a particular situation”

If the design patterns are 23 different applications of some underlying truth, this will make them object-oriented software development practices. Then what are the principles of object-oriented software development?

Here are my favorites among the design principles:

  1. Decoupling: Strive for loosely coupled designs between objects that interact.
  2. Open/Closed Principle: Classes should be open for extension, but cOpen/Closedlosed for modification (= classes should be easy to extend without needing to change them).
  3. Abstraction: Depend on abstractions, do not depend on concrete classes.
  4. Single Responsibility: A class should have only one reason to change.
  5. DRY (Don’t Repeat Yourself): Store information in a single place, do not scatter it throughout your code.

To exemplify, let’s look at design patterns that support some of the above principles. Two of the patterns that are closely related to Decoupling is Observer and Factory Method. Observer lets a class notify other classes without knowing about them. Factory Method lets one class create objects without knowing which type they are. Not knowing about each other typically favors a loosely coupled design.

Two of the patterns that support the Open/Closed Principle is Strategy and Template Method. Strategy lets you vary the behavior of an object as if the type was not fixed at compile-time. You determine the behavior by supplying a strategy object e.g. during construction. Template Method will let you implement parts of an algorithm. Typically, you subclass the algorithm and implement specialization in the subclass. Both let you change the behavior without changing the code.

If you adhere to the object-oriented principles above, design patterns come up naturally. I think this is also the best way to learn how to apply design patterns. Instead of actively looking for places where your design patterns could fit, use the principles and the patterns will follow.

Some of the design principles are hard to match with any of the design patterns. For example, Abstraction is typically something expressed in the domain language of the problem you’re trying to solve. Thus, it helps you to write code that relates better to your domain. The same goes for Single Responsibility, since the reason for change is domain specific. If you look at all design principles, it is clear that they are themselves special cases that can be divided into groups of related responsibility. It makes you wonder what is the underlying truth of the design principles?

Design Patterns: Elements of Reusable Object-Oriented Software

Design Principles by Example: Talk to an Interface or an Abstraction?

What is the relation between design principles “Talk to an interface, not an implementation” and “Talk to an abstraction, not a concrete”? When you apply them, you want to achieve different goals.

Two important design principles for writing good software are

  1. “Talk to an interface, not an implementation” and
  2. “Talk to an abstraction, not a concrete”.

Admittedly, they sound very much alike, so what is the difference between them?

communications towerAssume you are writing a client implementation that needs to communicate with a server somewhere. You have chosen to use a web socket for sending messages over the wire. To that end, you will use a class ClientWebSocketSenderImplementation. Now, the “Talk to interface, not an implementation” design principle suggests that talking to the web socket implementation class directly is inappropriate. Instead, you should talk to an interface ClientWebSocketSender.

Following the first design principle have several upsides. First, it will make your code easier to test. In this case, using the web socket implementation directly would use the network. That would make your unit tests slow and unreliable and might require a complicated setup phase. Second, talking to the web socket implementation directly would couple your client code to that specific implementation. If needed, changing web socket implementations would be difficult. Also, your code would not be reusable without shipping the web socket implementation.

We have chosen to send messages using a web socket. But there is really no need for our client application to know how messages are sent over the network. The second design principle says “Talk to an abstraction, not a concrete”. The concrete here is a web socket. A suitable abstraction in this context could be the ability to send messages over the web without specifying how. So we introduce an interface ClientWebSender. Depending on the application, we could take it one step further. It might make sense to abstract away the fact that we’re sending messages over the internet (for example, it could be over an IPC channel). We would end up with an interface ClientSender.

The second design principle will make your application more resilient to change. Without the abstraction, the web socket details might propagate throughout your code. For example, functions, arguments and return types could be specific to web sockets. If you would like to change your application to support message sending over e.g. HTTP or your own proprietary protocol over TCP, you would have to chase down all references to web sockets.

Last, the “Talk to an abstraction, not a concrete” does not require us to talk to an interface. You might have a class that represents the “abstraction” part and hides the “concrete” part (e.g. by delegating to a web socket implementation). So our two design principles serve different purposes and does not necessarily overlap. That said, they work very well in combination to write decoupled, testable and change resilient software.

Object-Orientation is not Really About Objects

How do we write good object-oriented programs? Despite the name, it is not by focusing on objects.

OrientationObject-oriented (OO) programming presents several concepts to the programmer not present in a procedural language. Given the name “object-orientation”, objects is clearly a central concept. Objects are the basic building blocks of our program and they carry out all the important work of our program. This should be contrasted with procedural programs, where functions are global and data is passed as arguments.

From time to time, you come across OO programs that has a procedural flavor to them. There are few, if any, interfaces and classes are primarily used to organize related functions with data. Admittedly, this is a slight administrative improvement over procedural programming with structs and global functions. The problem is, OO programs like this have the same problems as procedural programs: coupling.

Martin Fowler illustrates the concept very well in his article Design Principles and
Design Patterns
(Figure 2-17 on page 13). Assume you have a main function. Main calls three other functions, which in turn calls two other functions. Main now depends on all its descendants! High-level logic should not depend on low-level implementation.

The same applies to the “procedural style OO program” described above. Main might use three objects and call a function in each. Those functions might call two functions on some other classes and so forth. Main will depend on all classes and functions. Before you know it, main depends on your whole system.

The point is, even though objects do all the important work, they do not take us very far when trying to write good, decoupled software. Instead, have a look at Figure 2-18 in the article. Assume that main calls functions on three different interfaces. Main would not know or care which class implements each interface. Main depends only on the interfaces.

Thus, when writing object-oriented software, think primarily about abstractions and interfaces. After that, you can think about objects.

Duplication-Driven Development?

What is the primary force that drives you when doing test-driven development? Surprisingly, it might just be code duplication.

In the book “Test-Driven Development by Example“, Kent Beck uses an implementation of money as an example of TDD. At one point, a test case says “assert(someFunc(5) == 5 * 2)” and to satisfy the test case, someFunc is implemented simply by returning a hard-coded 10. “Now that is dirty” you say (and that’s what I said :). According to the book, the reason for hard-coding is that we want the test case to pass as soon as possible. After that, the refactoring phase can take over and we can clean up the code.

The Petronas Twin TowersHere comes the interesting part: hard-coding is a terrible habit, right? And we don’t want our bad habits to show up in the code. This sign of bad taste should be reason enough to fix it, right? However, there is a more fundamental reason for getting rid of the constant: code duplication. The constant 2 * 5 = 10 occurs both in the test case and in the code. To remove the duplication, change someFunc(param) to return “param * 2” (later, the book also gets rid of the 2 by replacing it with a member variable). To sum up, instead of acting on “taste”, we use the well proven design principle of (avoiding) code duplication.

A fascinating insight from the book is that the design of the code, too, can grow naturally from code duplication. For example, let’s say we have a class that encapsulates some algorithm. When another class with similar behavior is required, the book suggests we can accept some code duplication (even extensive copy-paste) to pass the tests. After getting the tests to pass, we refactor. Lets say the difference between the two classes is the fact that they use different algorithms to carry out their work. Apart from that, they are identical. To avoid code duplication, we combine the two classes. Quite naturally, this results in us isolating the algorithms behind an interface. By following the path of least resistance for removing code duplication, we have implemented the strategy pattern. To summarize, this suggests that code duplication helps us drive the high-level design of our code. If true, it is kind of a comforting feeling that refactoring and code duplication naturally leads us to good design.

All and all, “Test-Driven Development by Example” is well-written, easy to read and quite a good book, albeit maybe more suitable for the aspiring test-driven designer than the seasoned on.

Great Books for a Software Developer

I have received questions about good books, so I thought I would share some titles with you. First, my absolute favorites, should be mandatory reading for everybody (so go ahead and read them right away):

  • Growing Object-Oriented Software Guided by TestsGrowing Object-Oriented Programs Guided by Tests” by Steve Freeman and Nat Pryce (Amazon). My bible. :) A fantastic book about test-driven development. I touched upon it in my post Test-Driven Development Done Right.
  • Implementing Lean Software Development: From Concept to Cash” by Mary and Tom Poppendieck (Amazon). An amazing book on continuous improvement and customer focus in your software development organization. See also my post Lean Software Development.
  • Code Complete, 2nd edition” by Steve McConnell (Amazon). A great and practical book on how to write good code.

Really good books (read them when you finish the above :):

  • The Fifth Discipline: The Art & Practice of the Learning Organization” by Peter Senge (Amazon). A fantastic book on how to make people work together so that your organization becomes greater than the sum of the individuals. Not really a software development book, that’s the only reason why it didn’t make it to the “mandatory reading” list. :)
  • Design Patterns: Elements of Reusable Object Oriented Software” by Erich Gamma et. al. (Amazon). The Book on design patterns. A must-have.
  • Peopleware: Productive Projects and Teams” by Tom DeMarco and Timothy Lister (Amazon). A book on what is required to create an productive software development organization. Pretty interesting facts in there.
  • How to Win Friends and Influence People” by Dale Carnegie (Amazon). A classic about how to build good relationships and how to handle people.
  • One Minute Manager” by Kenneth Blanchard and Spencer Johnson (Amazon). A classic about how to manage people in order to get them as happy and productive as possible.