It is surely becoming a habit of me to write about design patterns the last few weeks, and I attribute that largely to my studying of them on Pluralsight (which by the way is a really great learning platform, go sign up for their premium plan if you haven't already), so I like sharing the knowledge I gain about them on the free, non-paywalled internet.
This week we have a whopping 5 patterns to talk about. Well, actually 4 patterns and one mini-pattern, as the Null Object pattern is extremely simple. So without further ado, let's go over the Adaptor, Null Object, Specification, State, and Mediator patterns. That is a lot of ground to cover so I will try to keep everything as simple as possible.
Adaptor
The function of this pattern should be clear from its name - its job is to wrap a class into a new class to make it compatible with the other design patterns. Basically, it works by keeping a private object instance of the incompatible class inside the new class. Then it adds methods that correspond to the incompatible class' methods and calls them after executing any domain logic that other patterns might have added, before and after the incompatible method is called privately.
This is a must-use pattern in codebases where there's a ton of legacy code and it's infeasible to port it all to use modern features. It allows these classes to take advantage of these newer features without having to rewrite the entire class. Sometimes you may not even be allowed to modify the legacy class. Financial transaction-processing software used in banks is a prime example of this.
Because of the loose coupling between the incompatible class and the rest of the program or library, testability is improved as well. The easiest classes to wrap an adapter class around them are the ones that follow the single-responsibility and interface-segregation principles, as these classes aren't calling a whole bunch of subsystems in their methods.
The adaptor pattern cannot only wrap subjects (such as functionality), it can also wrap return values into a type that makes it identical to the type that all other classes expect to deal with.
Null Object
A convenient way to define a NULL object for your class without using the dreaded NULL constant itself? Did you know that Sir Tony Hoare, the inventor of NULL (and also quicksort by the way, among other things), regarded it as his billion-dollar mistake, referring to the money lost by businesses debugging problems related to NULL references? So the use of the NULL pointer is heavily frowned upon.
The alternative for using NULL is very simple. Simply make a subclass of your class that initializes all of your member variables to default, programmer-defined values. Then you can make a static method inside the parent class called Null()
or similar that returns an instance of this "Null" subclass. That's all there is to it.
Specification
No, not a specification for even more design patterns. The specification pattern lets you define expressions that other objects can "pass" or "fail" against respectively. It helps avoid duplicating domain logic (the conditional if
statements against data) by keeping it all in one location.
Let's say that your class defines different qualities for its object, and you would like to test if your class objects were initialized with, or set to, a certain quality. And you have to do this check inside multiple functions. Instead of spelling out the exact same conditional statement in all of those places, you put that conditional statement in a method inside the Specification class that's just for evaluating tests, and then instantiate Specification class objects where you can pass arbitrary classes to its Test method to get a boolean result.
Specifically, it will work like this: You define an abstract class that has a single method, Test()
which takes some other - but only one - class object as its method, and it returns a boolean True or False value, for whether the class object meets its specification.
Then you subclass the specification class as many times as you want and in each one, you hard-code your own expression that you want to use.
It is even possible to code a specification class that returns a default True value, "And" and "Or" specification class that chain two specifications together, and a "Not" class, all while adhering to this template. Take a look at the code example below in C++:
State
The State pattern provides a container that enumerates all possible states its encapsulating object is allowed to take. It solves the major problem of what I call "boolean hell" where all the state is represented by multiple booleans and enumeration values. This flood of variables results in you having to test all permutations of possible boolean/enum combinations to check for an invalid state.
There are two problems with doing this: One, you're modifying the codebase, so every time you add a new state variable, you have to go back to the class methods and insert checks for this state in there, potentially introducing bugs. And Two, this quickly becomes infeasible once your class has several booleans. Both of these effects result in people being reluctant to edit the codebase, which would be detrimental to applications.
In this pattern, the State class is abstract, and all the concrete state classes subclass it. The abstract state class will have an ID for identification purposes, which each subclass has to define to a unique value. All the functions that modify state are also defined as abstract methods in the abstract class, but these actually call the real methods inside a Context class that's passed as a parameter to all the methods. This Context class is your class for which you want to implement state for.
This method allows you to mark certain functions as "impossible" by throwing an exception if it's not supported by the current state.
Mediator
The final design pattern I will cover this week is the Mediator design pattern. And it's better that I include the UML diagram for it since it can get quite complicated.
The mediator pattern is supposed to wire all the different subsystems together to make it easier for them to communicate with each other. It requires all the subsystems to inherit a common base class that has a method for receiving messages, and also the Mediator rendezvous must also inherit a base class that contains the method for sending messages.
One use case for this pattern is if you suddenly get a signal from the operating system to reload configuration files or terminate, you can send a message to all subsystems to clean up or refresh themselves accordingly.
Thanks for reading. Let me know if you have any comments you have about these patterns.
Top comments (0)