There are plenty of articles that explain what design patterns are, and how to implement them; the web doesn't need yet another one of those articles! Instead, in this article, we will more discuss the when and why, rather than the which and how.
I'll present different situations and use-cases for patterns, and will also provide short definitions to help those of you who are not so familiar with these specific patterns. Let's get started.
Every few weeks, we revisit some of our reader's favorite posts from throughout the history of the site. This tutorial was first published in October, 2012.
This article covers some of the various Agile Design Patterns, documented in Robert C. Martin's books. These patterns are modern adaptations of the original design patterns defined and documented by The Gang of Four in 1994. Martin's patterns present a much more recent take on the GoF's patterns, and they work better with modern programming techniques and problems. In fact, about 15% of the original patterns were replaced with newer patterns, and the remaining patterns were slightly modernized.
Let's Start by Creating Some Objects
Use a Factory Pattern
The factory pattern was invented to help programmers organize the information related to object creation. Objects sometimes have lot of constructor parameters; other times, they must be populated with default information immediately after their creation. These objects should be created in factories, keeping all the information regarding their creation and initialization contained within a single place.
When Use a Factory Pattern when you find yourself writing code to gather information necessary to create objects.
Why: Factories help to contain the logic of object creation in a single place. They can also break dependencies to facilitate loose coupling and dependency injection to allow for better testing.
Finding the Data We Need
There are two frequently used patterns to retrieve information from a persistence layer or external data source.
The Gateway Pattern
This pattern defines a communication channel between a persistence solution and the business logic. For simpler applications, it can retrieve or recreate whole objects by itself, but object creation is the responsibility of factories in most complex applications. Gateways simply retrieve and persist raw data.
When: When you need to retrieve or persist information.
Why: It offers a simple public interface for complicated persistence operations. It also encapsulates persistence knowledge and decouples business logic from persistence logic.
In fact, the gateway pattern is just a particular implementation of another design pattern that we'll discuss shortly: the adapter pattern.
Go with the Proxy
There are times when you can not (or do not want to) expose the knowledge of the persistence layer to your business classes. The proxy pattern is a good way to fool your business classes into thinking they are using already existing objects.
When: You have to retrieve information from a persistence layer or external source, but don't want your business logic to know this.
Why: To offer a non-intrusive approach to creating objects behind the scenes. It also opens the possibility to retrieve these object on the fly, lazily, and from different sources.
A proxy effectively implements the same interface as a real object and mimics its functionality. The business logic simply uses it as if it were a real object, but in fact, the proxy creates the object if one doesn't exist.
The active object pattern also played a part in early multi-tasking systems.
Okay, okay. That great and all, but how can we find the objects that we need to create?
Ask a Repository
The repository pattern is very useful for implementing search methods and mini-query languages. It takes these queries and uses a gateway to obtain the data for a factory to produce the objects you need.
The repository pattern is different from the other patterns; it exists as part of Domain Driven Design (DDD), and is not included as part of Robert C. Martin's book.
When: You need to create multiple objects based on search criteria, or when you need to save multiple objects to the persistence layer.
Why: To let clients that need specific objects to work with a common and well isolated query and persistence language. It removes even more creation-related code from the business logic.
But what if the repository cannot find the objects? One option would be to return a
NULL value, but doing so has two side effects:
- It throws a refused bequest if you try to call a method on such an object.
- It forces you to include numerous null checks (
if(is_null($param)) return;) in your code.
A better approach is to return a
The Null Object Pattern
A null object implements the same interface of your other objects, but the object's members return a neutral value. For example, a method that returns a string would return an empty string; another member returning a numeric value would return zero. This forces you to implement methods that do not return meaningful data, but you can use these objects without worrying about refused bequest or littering your code with null checks.
When: You frequently check for
null or you have refused bequests.
Why: It can add clarity to your code and forces you to think more about the behavior of your objects.
It's not unusual to call many methods on an object before it can do its job. There are situations when you must prepare an object after its creation before you can truly use it. This leads to code duplication when creating those objects in different places.
You Need the Command Pattern
When: When you have to perform many operations to prepare objects for use.
Why: To move complexity from the consuming code to the creating code.
This sounds good, doesn't it? In fact, it is quite useful in many situations. The command pattern is widely used for implementing transactions. If you add a simple
undo() method to a command object, it can track all the undo transactions it performed and reverse them if necessary.
So now you have ten (or more) command objects, and you want them running concurrently. You can gather them into an active object.
The Active Object
The simple and interesting active object has only one responsibility: keep a list of command objects and run them.
When: Several similar objects have to execute with a single command.
Why: It forces clients to perform a single task and affect multiple objects.
An active object removes each command from its list after the command's execution; meaning, you can execute the command only once. Some real world examples of an active object are:
Design patterns are here to solve problems.
- Shopping Cart - Executing a
buy()command on each product removes them from the cart.
- Financial Transactions - Grouping transactions into a single list and executing them with a simple call to the list manager's active object would remove the transactions from the queue.
The active object pattern also played a part in early multi-tasking systems. Each object inside an active object would keep a reference to the active object. They would execute a portion of their jobs and then put themselves back into the queue. Even in today's systems, you can use an active object to let other objects work while you wait for a response from another application.
I am positive that you've heard the big promise of object oriented programming: code reuse. Early adopters of OOP envisioned using universal libraries and classes in millions of different projects. Well, it never happened.
Make Some Template Methods Instead
This pattern allows for the partial reuse of code. It's practical with multiple algorithms which only slightly differ from one another.
When: Eliminate duplication in a simple way.
Why: There is duplication and flexibility is not a problem.
But flexibility is nice. What if I really need it?
It's Time For a Strategy
When: Flexibility and reusability is more important than simplicity.
Why: Use it to implement big, interchangeable chunks of complicated logic, while keeping a common algorithm signature.
For example, you can create a generic
Calculator and then use different
ComputationStrategy objects to perform the calculations. This is a moderately used pattern, and it is most powerful when you have to define many conditional behaviors.
As projects grow, it becomes increasingly difficult for external users to access our application. That's one reason to offer a well-defined entry point to the application or module in question. Other such reasons may include the desire to conceal the module's internal workings and structure.
Present a Facade
A facade is essentially an API - a nice and client-facing interface. When a client calls one of these nice methods, the facade delegates a series of calls to the classes it hides in order to provide the client with the required information or desired result.
When: To simplify your API or intentionally conceal inner business logic.
Why: You can control the API and the real implementations and logic independently.
Control is good, and many times you need to perform a task when something changes. Users have to be notified, red LEDs have to blink, an alarm has to sound... you get the idea.
The popular Laravel framework makes excellent use of the Facade Pattern.
Subscribe to an Observer
A null object implements the same interface as your other objects.
The observer pattern offers an easy way to monitor objects and take actions when conditions change. There are two types of observer implementations:
- Polling - Objects accept subscribers. Subscribers observe the object and are notified on specific events. Subscribers ask the observed objects for more information in order to take an action.
- Push - Like the polling method, objects accept subscribers, and subscribers are notified when an event occurs. But when a notification happens, the observer also receives a hint that the observer can act on.
When: To provide a notification system inside your business logic or to the outside world.
Why: The pattern offers a way to communicate events to any number of different objects.
Use cases for this pattern are email notifications, logging daemons, or messaging systems. Of course, in real life, there are countless other ways to use it.
Coordinate The Effects
The observer pattern can be extended with a mediator pattern. This pattern takes two objects as parameters. The mediator subscribes itself to the first parameter, and when a change happens to the observed object, the mediator decides what to do on the second object.
When: The affected objects can not know about the observed objects.
Why: To offer a hidden mechanism of affecting other objects in the system when one object changes.
Sometimes, you need special objects that are unique in your application, and you want to ensure that all consumers can see any change made to these objects. You also want to prevent creating multiple instances of such objects for certain reasons, like long initialization time or problems with concurrent actions to some third party libraries.
Use a Singleton
A singleton is an object having a private constructor and a public
getInstance() method. This method ensures that only one instance of the object exists.
When: You need to achieve singularity and want a cross platform, lazily evaluated solution which also offers the possibility of creation through derivation.
Why: To offer a single point of access when needed.
Or Write a Monostate Object
Another approach to singularity is the monostate design pattern. This solution uses a trick offered by object oriented programming languages. It has dynamic public methods which get or set the values of static private variables. This, in turn, ensures that all instances of such classes share the same values.
When: Transparency, derivabitility, and polymorphism are preferred together with singularity.
Why: To hide from the users/clients the fact that the object offers singularity.
Pay special attention to singularity. It pollutes the global namespace and, in most cases, can be replaced with something better suited for that particular situation.
Controlling Different Objects
The repository pattern is quite useful for implementing search methods...
So you have a switch and a light. The switch can turn the light on and off, but, now, you've purchased a fan and want to use your old switch with it. That's easy to accomplish in the physical world; take the switch, connect the wires, and viola.
Unfortunately, it's not so easy in the programming world. You have a
Switch class and a
Light class. If your
Switch uses the
Light, how could it use the
Easy! Copy and paste the
Switch, and change it to use the
Fan. But that's code duplication; it's the equivalent of buying another switch for the fan. You could extend
FanSwitch, and use that object instead. But what if you want to use a
RemoteControl, instead of a
The Abstract Server Pattern
This is the simplest pattern ever invented. It only uses an interface. That's it, but there are several different implementations.
When: You need to connect objects and maintain flexibility.
Why: Because it is the simplest way to achieve flexibility, while respecting both the dependency inversion principle and the open close principle.
PHP is dynamically typed. This means that you can omit interfaces and use different objects in the same context - risking a refused bequest. However, PHP also allows for the definition of interfaces, and I recommend you use this great functionality to provide clarity to the intent of your source code.
But you already have a bunch of classes you want to talk to? Yes, of course. There are many libraries, third-party APIs, and other modules that one has to talk to, but this does not mean that our business logic has to know the details of such things.
Plug in an Adapter
The adapter pattern simply creates a correspondence between the business logic and something else. We have already seen such a pattern in action: the gateway pattern.
When: You need to create a connection with a pre-existing and potentially changing module, library, or API.
Why: To allow your business logic to rely only on the public methods the adapter offers, and permit changing the other side of the adapter easily.
If either of the above patterns don't fit with your situation, then you could use...
The Bridge Pattern
This is a very complicated pattern. I personally do not like it because it is usually easier to take a different approach. But for those special cases, when other solutions fail, you can consider the bridge pattern.
When: The adapter pattern is not enough, and you change classes on both sides of the pipe.
Why: To offer increased flexibility at the cost of significant complexity.
The Composite Pattern
Consider that you have a script with similar commands, and you want make a single call to run them. Wait! Didn't we already see something like this earlier? The active object pattern?
Yes, yes we did. But this one is a bit different. It's the composite pattern, and like the active object pattern, it keeps a list of objects. But calling a method on a composite object calls the same method on all of its objects without removing them from the list. The clients calling a method are thinking they are talking to a single object of that particular type, but in fact, their actions are applied to many, many objects of the same type.
When: You have to apply an action to several similar objects.
Why: To reduce duplication and simplify how similar objects are called.
Here's an example: you have an application that is capable of creating and placing
Orders. Assume you have three orders:
$order3. You could call
place() on each of them, or you could contain those orders in a
$compositeOrder object, and call its
place() method. This, in turn, calls the
place() method on all the contained
The State Pattern
Gateways only retrieve and persist raw data.
A finite state machine (FSM) is a model that has a finite number of discreet states. Implementing a FSM can be difficult, and the easiest way to do so involves the trusty
switch statement. Each
case statement represents a current state in the machine, and it knows how to activate the next state.
But we all know that
switch...case statements are less desirable because they produce an unwanted high fan-out on our objects. So forget the
switch...case statement, and instead consider the state pattern. The state pattern is composed of several objects: an object to coordinate things, an interface representing an abstract state, and then several implementations - one for each state. Each state knows which state comes after it, and the state can notify the coordinating object to set its new state to the next in line.
When: FSM-like logic is required to be implemented.
Why: To eliminate the problems of a
switch...case statement, and to better encapsulate the meaning of each individual state.
A food dispenser could have a
main class that has a reference to a
state class. Possible state classes might be something like:
ReturningChange. Each state performs its job and creates the next state object to send to the coordinator class.
Decorate with the Decorator Pattern
There are times when you deploy classes or modules throughout an application, and you can't modify them without radically affecting the system. But, at the same time, you need to add new functionality that your users require.
The decorator pattern can aid in these situations. It is very simple: take existing functionality and add to it. This is accomplished by extending the original class and providing new functionality at run-time. Old clients continue to use the new object as they would an old one, and new clients will use both the old and new functionality.
When: You can't change old classes, but you have to implement new behavior or state.
Why: It offers an unintrusive way of adding new functionality.
A simple example is printing data. You print some information to the user as plain text, but you also want to provide the ability to print in HTML. The decorator pattern is one such solution that lets you keep both functionality.
Or, Accept a Visitor
If your problem of extending functionality is different - say, you have a complex tree-like structure of objects, and you want to add functionality to many nodes at once - a simple iteration is not possible, but a visitor might be a viable solution. The downside, however, is that a visitor pattern implementation requires modification to the old class if it wasn't designed to accept a visitor.
When: A decorator is not appropriate and some extra complexity is acceptable.
Why: To allow and organized approach to defining functionality for several objects but at the price of higher complexity.
Use design patterns to solve your problems, but only if they fit.
Design patterns help solve problems. As an implementation recommendation, never name your classes after the patterns. Instead, find the right names for the right abstractions. This helps you to better discern when you really need a pattern as opposed to just implementing one because you can.
Some may say that if you don't name your class with the pattern's name in it, then other developers will have a difficult time understanding your code. If it's hard to recognize a pattern, then the problem is in the pattern's implementation.
Use design patterns to solve your problems, but only if they fit. Do not abuse them. You'll find that a more simple solution befits a little problem; whereas, you'll discover that you need a pattern only after you implement a few other solutions.
If you're new to design patterns, I hope that this article has given you some idea as to how patterns can be helpful in your applications. Thanks for reading!