Design Patterns VS Design Principles: Observer, State and Strategy
In this series of articles on design patterns and design principles, we analyse how the 23 GoF design patterns relate to the 9 GRASP design principles.
In a previous article, we classified the GRASP patterns like this (excluding “Managing complexity”):
The study we perform in this series is helpful to understand better both the GoF and the GRASP, with the goal of taking better decisions when it comes to organizing our code.
The GoF design patterns come from the seminal Design Patterns book. The GRASP design principles are explained in Craig Larman’s Applying UML and Patterns.
In this episode of the series, we examine the Observer, State and Strategy design patterns.
Observer
The Observer patterns is about notifications between objects.
Consider an object A that can undergo events, in the general sense of “events”. That can be GUI events, or changes of state, or anything that could be of interest for another object B. A is called Subject and B is called Observer:
A typical example is indeed for GUI events, for example the user has clicked on a widget. The class handling the GUI event needs to notify the business objects of the program that the event has happened, so that they can react accordingly.
Observer is not limited to GUI though. We can see it at any level of a program. For example, Qt’s signals and slots are an implementation of the Observer pattern.
A given Subject can have an arbitrary number of Observers:
A central aspect of the Observer design pattern is that the Subject doesn’t know what the Observers are. It just knows that they exist and how to notify them. To implement this aspect, we can use polymorphism, for example with inheritance:
The Observer interface could be implemented like this:
class Observer { virtual void onNotification() = 0; virtual ~Observer(); };
And the Subject could hold a std::vector<Observer*>
, and traverse it to call onNotification()
on each element, whenever the Subject needs to notify its observers.
Design principle
To which GRASP principle does the Observer design pattern relate most?
In my opinion, the central aspect of the Observer design pattern is the dedicated Observer
interface, that allows for the Subject not to know the nature of its observer(s).
This interface creates a layer around the Observer objects, allowing them to change without affecting the Subject. This helps creating Low Coupling, in a way that looks like Protected Variations.
Even though we implemented the Observer design pattern by using polymorphism, it seems like an implementation detail to me rather than the essence of the Observer design pattern.
State
The State design pattern is useful when an object can be in several formalized states. The GoF book takes the example of a TCP connection that can be Established
, Listening
or Closed
.
There are many other examples in various fields of objects that can be in several states. For example a financial operation could be PendingApproval
, Approved
or Closed
. Or in a project management application, a task could be ToDo
, Doing
, ReadyForTesting
or Done
One way to implement this is to have a constant or enum representing each state, and a value that can be equal to either one of the possible states:
enum class TaskState { ToDo, Doing, ReadyForTesting, Done };
With the task having the following member:
class Task { public: // ... private: TaskState currentState_; // ... };
This way of representing the state in code can lead to complicated code, with if statements that test the possible values of currentState_
. Depending on those values, the if statements would perform actions and/or modify the value of currentState_
.
When such code grows in size, it typically grows in complexity. That is to say that this way of coding doesn’t scale.
Why? Because it is the same code that handles all of the various possible states and their changes.
The State design patterns aims at solving this problem by representing each state by a type, and not by just the value of an enum:
class ToDo { // ... }; class Doing { // ... }; class ReadyForTesting { // ... }; class Done { // ... };
Each class owns the code of its corresponding state. Each class also manages the transitions to another state, by instantiating the corresponding class.
The GoF book suggests to have all such state classes inherit from a common base class. This allows to implement state changes by swapping the concrete implementation behind a pointer or reference to the base class.
The State design pattern can also be implemented with templates and phantom types. This is an interesting topic, that we’ll leave for a dedicated post.
Design principle
Now to which of the 9 GRASP design principles does the State design pattern relate most?
The purpose of State is to avoid having the same code deal with the various states implementations and transitions. Instead, it introduces classes that are focused on a single purpose: handling one particular state.
Seen this way, State is a way to achieve High Cohesion.
Can we find a refinement of High Cohesion that correspond better to State?
State is arguably not a Pure Fabrication, as the various states of an object often exist in the domain. In all our above examples (the Established
, Listening
or Closed
TCP connection, the PendingApproval
, Approved
or Closed
trade, and the ToDo
, Doing
, ReadyForTesting
or Done
task) the states mapped well to domain concepts.
It’s also not an Information Expert, as it’s not about which class has the required data to perform an operation. And it’s clearly not a Creator.
So I would argue that State is another way to achieve High Cohesion.
Strategy
The Strategy design pattern consists in letting a calling code use one of several possible algorithms, by hiding them behind an interface.
For example, let’s consider a certain task X that has three way of being accomplished: method A, method B and method C. Rather than having A, B and C directly in the code that needs to perform X, we use three classes that implement the same interface, each one implementing one of the three methods.
Some other part of the code sets either one of MethodA
, MethodB
or MethodC
as a concrete implementation of the interface.
Even if Strategy allows to achieve cohesion by having each algorithm put away in its own class, it seems to me that the central aspect of Strategy is rather Polymorphism.
Three more patterns
Our analysis led us to make the following associations:
- Observer implements Protected Variations,
- State implements High Cohesion,
- Strategy implements Polymorphism.
Like in the other articles in this series, those are not maxims that you need to embrace. Classifying has a part of subjectivity, because it depends on what you perceive as the essential aspect of each pattern.
If you would have classified some of them differently, I’d love to hear your thoughts. Leave a comment below.
You will also like
- GRASP: 9 Must-Know Design Principles for Code
- Design Patterns VS Design Principles: Chain of responsibility, Command and Interpreter
- Design Patterns VS Design Principles: Iterator, Mediator and Memento
- It all comes down to respecting levels of abstraction
Share this post!