In a previous article published on CodeX, I wrote about the Proxy, Flyweight, and Strategy design patterns: Design Patterns Cheat Sheet - though "Cheat Sheet" might have been the wrong words to describe it. So the next patterns that I would like to show you are four: Singleton, Command, Factory, and Decorator.
The Singleton pattern ensures only one instance of a class ever exists during the application's lifecycle. It guarantees this by utilizing a private constructor and a public static
Create() method, which first checks if the singleton object exists. If it doesn't, it allocates a new one and returns that. Else, it just returns the singleton that you already created.
Singleton objects can either be created lazily, as the application needs them or right away when the program starts.
A singleton does not have to be implemented as a static class. In some languages, this allows you to pass singletons as arguments, implement interfaces and serialization methods.
Under the hood, a singleton object uses its own private lock to guard the possibility of multiple threads calling the singleton
Create() function at once, which otherwise would've caused undefined behavior.
As I mentioned earlier, singleton classes have a private constructor. You cannot subclass a singleton object because that would violate the principles of this design pattern. Singleton
Create() methods also don't take parameters.
Some people call the Singleton pattern an antipattern because of its difficulty to test, particularly due to the singleton being shared by the whole program. The resolution for that is ''to create an interface, or base class that the singleton can implement so that you can make another implementation or class of the singleton object, that only has mock functions.
The command pattern lets you enforce Separation of Concerns by abstracting each task into its own class. One benefit of this design pattern is that it is easier to make new versions of tasks and substitute them into programs without introducing a critical error in your program because the tasks are extracted from the main functions.
Like in the Singleton pattern, you can create an interface or base class and then implement or subclass to get another version of this design pattern that is just a test or mock implementation. These can be used in unit tests.
The command pattern has all the information it needs to execute the command again. So it can easily be adapted to provide undo & redo functionality by keeping a sequence of Command objects together. Then you can iterate through them in reverse order and apply their Undo method. Then you can go forwards and call their Run method again.
The factory pattern is a prevalent pattern that decouples requesting a new object from creating it. It makes classes more extensible by allowing factories and factory items to be subclassed, with each subclass doing something different.
In factory items, it's to create a slightly different type of item (as many factories make more than one product model). For the factories themselves, it is to specify their traits, qualities, conditions under which items should be created - just like factory building qualities.
Factory subclasses tend to perform some initialization to all created objects, making the metaphor kind of like real factories that set different settings for their finished products.
The factory classes themselves have a protected constructor and a public static
Create() method that takes parameters that decide which factory item subclass to return. Some classes abstract the object initialization logic to a public static
Get() method and make the static Create method protected. The base classes for factories and factory items are always abstract, meaning that subclassing is required.
Finally, we arrive at the last pattern of this article. The Decorator pattern has the unique ability to wrap around any object and run additional functions before or after it, or even conditionally disable the function invocation by making the decorator avoid calling the function. It is ubiquitous in the programming language Python.
Decorators are extremely useful for adding compounded functionality to a function or class without modifying the target class or function itself. It also lets you maintain a strong separation of concerns in your classes.
The decorator class is implemented by creating an interface of an abstract base class with all necessary functions defined in it and by making a subclass or implementation of all these functions in the real subclass in addition to the "decorated" subclasses, each of which eventually calls the corresponding real class' method in the process of running the injected code.
Multiple decorators can be applied to a single function. The innermost decorators will be executed first. Then the runtime will pass control to the next outer decorator, and so on.
For classes and functions that don't have interfaces or base classes, you can define that hierarchy using an "Adapter pattern" (more on this next week). The only difference will be, instead of having the real class or function, your subclass will be the adapters' class, which will call the corresponding function of the real class.