Yet Another Post About Design Patterns

This post is about design patterns. Not like most of the posts available on the internet, it does not focus on the details of each pattern and how to implement it. This post gives just a brief introduction on each pattern, mostly Gang of Four (GoF)-Pattern. The main focus of this post is to show you real-world examples which explain in simple scenarios how to use a pattern. Compared to other posts about design pattern I would like to describe trade-offs and when certain patterns should be avoided instead of highlighting only the advantages. Usually, a pattern is chosen to solve a certain problem because the trade-offs of the pattern are not expected to be relevant in this specific problem statement.

All examples are hosted on GitHub and can be accessed via the following url:

Structural Patterns

Adapter Pattern

Adapter pattern allows integrating other systems, components or libraries without spreading their implementation details across the whole system. The adapter pattern is one of the most important patterns in Service-Oriented Architectures which reduces maintenance effort when integrating external services.

Example on github

The example shows how the legacy high resolution timers of windows (LegacyHighResolutionTimer) can be integrated without working with them directly. Instead, the wrapper (IHighResolutionTimerAdapter) can be used internally. This gives the flexibility to replace the legacy timer implementation easily at any time. In this example the implementation could be replaced with a Linux-based solution, for instance, when the code has to run on another platform.

The adapter pattern leads to additional boilerplate code (the adapter itself) which has to be maintained.

Proxy Pattern

The proxy pattern is similar to the adapter pattern but instead of adapting the interface of the class it just forwards the calls one-by-one and implements the same interface like the class it is wrapping.
Usually, the adapter pattern should be preferred because it gives the flexibility of how to design the interface for the consumers.

Example on github

Classical example of how a profiler can be implemented as a proxy. The class ProfilerUrlRequest implements the same interface like the actual class UrlRequest and just forwards every single call to this class. Before and after the calls the profiling code can be placed.

Like the adapter pattern the proxy pattern leads to boilerplate code which has to be maintained. This additional level of abstraction makes it also more difficult to find out which class provides the needed functionality.

Facade Pattern

The Facade pattern helps hiding the complexity of a system. It provides a simplified interface for the consumer. The implementation of the facade usually calls multiple classes and cumulates the output.
When designing a 4-layer application the second layer is usually implemented using the facade pattern. The top layer is the presentation layer, the third layer is the business / domain layer and the forth is the data access layer. The second layer acts as a facade to abstract the complexity of the business layer and simplifies access to it. The User interface needs in most of the cases access to multiple business classes to be able to render the view. In order to avoid too much code in the presentation layer the so called Application Service Layer (see Domain-Driven Design) can help abstracting the complexity.

Example on github

The OrderApplicationServiceFacade implements a method to place an order which has to do multiple steps like validating the input, calculating the price and finally placing the order. This involves multiple business objects like Order and Pricing.

The advantage of the facade pattern to simplify the interface for the consumer can turn out to be a limitation in the system. If administrative users need to have access to special features the interface of the facade can be not sufficient and lead to many simple forwarding methods which are difficult to maintain.

Decorator Pattern

Every constructor takes an instance of the same abstract class. The implementation of streams in the .NET Framework is a good example for the decorator pattern. BufferedStream, MemoryStream, GZipStream take all the abstract Stream class in their constructors and allow to add additional functionality on top of other implementations.

Example on github

The example on github shows how to implement a concrete decorator for the Stream classes. The ThrottledReadStream class can be used to decorate existing streams and add additional functionality to throttle this stream. It is possible to throttle FileStreams, MemoryStreams or any other Stream.

The decorator pattern can lead to many similar objects which can be hard to maintain.
For the developer it can be difficult to understand how to use the different objects and how to combine them.

Bridge Pattern

The Bridge Pattern allows to abstract sub-classes by forwarding certain parts of the implementation.

Example on github

The class Cart is an aggregate and contains an instance of the Customer class. The Cart forwards certain method calls to Customer class which does the actual work. The Cart acts a bridge.

Similar drawbacks like the Adapter pattern.

Creational Patterns

Factory Method Pattern

The factory method pattern makes it possible to decide at runtime which implementation to take. This can be based on configuration settings or just dynamically. It creates an instance of a class which fulfills a certain interface. The client will just work with the interface and does not have to know which implementation is used.

Example on github

There are two examples on github. The first example shows how to implement a configurable Factory Method which decides based on configuration settings which implementation to take. In the second example (see UML diagram) the ImageConverterFactory chooses the right Image Converter (PngImageConverter, GifImageConverter) based on the header data of the byte stream.

All the classes being used by the Factory Method have to follow the exact same interface.
Compared to the abstract factory the client is bound to the Factory and the Factory Method cannot be exchanged easily.

Abstract Factory Pattern

The abstract factory pattern is pretty similar to the factory method pattern. It is actually an object which implements multiple factory methods. Hence, it can be seen as a more flexible approach but introduces also more complexity.

Example on github

In this example the ITaxCalculatorFactory is implemented by different classes. Based on the location of the customer the right TaxCalculatorFactory is chosen (Germany or England in this example). The ITaxCalculatorFactory consists usually of multiple Factory Methods to get the right implementation for each use-case.

The Abstract Factory cannot be easily extended to support multiple Factory Methods. Adding a new Factory Methods means changing the interface of the Abstract Factory and violates the Open-Close principle.

Builder Pattern

The most famous implementation of the Builder Pattern is the StringBuilder in .NET and Java. It allows splitting the creation of an object into multiple steps.

Example on github

In this example the creation of a news article is split into multiple methods, BuildAuthor, BuildHeadLine and BuildText.

The Builder should not be used for objects with just a few fields.

Prototype Pattern

The prototype pattern is used to create a new object based on an existing one. Usually it is used to clone objects.

Example on github

This is the classical example which shows how the Prototype Pattern is implemented to clone objects.

Each class has to implement its own clone method and therefore has to be adapted internally in order to support the prototype pattern. This can be difficult if external libraries are used.

Singleton Pattern

The singleton is one of the most used design patterns. It allows to create only one instance of a class. Singletons are usually used to coordinate operations in applications.

Example on github

The example shows how to implement a counter using the Singleton pattern. There exists only one instance of the IncrementalCounterSingleton which holds the current count. Double-Checked Locking Pattern is used to avoid multiple initialization and thread-safety.

Singletons are very difficult to implement in multi-threaded/multi-process environments.
Singletons usually turn out to be the bottleneck of implementations because they usually need synchronization which slows down the system.

Behavioral Patterns

Visitor Pattern

The visitor pattern allows defining new operation on classes without actually changing the class itself. It is possible to maintain a state between the visits of different classes which is especially in the compiler construction important.

Example on github

The classical examples for Visitors are SyntaxTrees which are used in the compiler construction. The structure of languages (classes for statements, etc...) is changed much rarely than new algorithms (operations on classes) are added, changed or removed to analyze the source code.

If the visited data structures (classes) change frequently the visitor pattern is not a good fit. The visitor interface has to be changed anytime a new class is added or an existing one is removed.
The declaration of the visit methods is static and has to be defined in advance.

Command Pattern

The command is an object which contains all the necessary information to execute an action at a later point of time.
The command pattern is used in command query responsibility segregation (CQRS) architectures where the actions (commands) are separated from the queries. Command pattern is used to implement Redo/Undo operations.

Example on github

The example on github shows how a calculator with undo functionality can be implemented using the Command pattern. The unit test class CommandTest shows how a stack can be used to keep a undo history.

Sometimes it is very difficult to store all the information needed to execute a command.
Additionally, CQRS architectures result usually in code with many small command classes.

Strategy Pattern

This pattern allows selecting the algorithm during runtime. It makes it possible to exchange algorithms easily.

Example on github

The example shows how the strategy pattern can be used to decide at runtime which sorting algorithm to use. There are two sorting strategies implemented: Bubblesort and Quicksort. One of these two algorithms is selected based on the number of elements which are passed to the Sort Selector class.

The strategy selector must have all the data which is needed for all supported strategies. If the input data varies for each strategy, the selector class gets quite complicated.

No comments:

Post a Comment