#
Design Patterns
A design pattern is a generalizable solution to a common problem that we're attempting to address. Design patterns in software development are one case of formalized best practices, specifically around how to structure your code to address specific, recurring design problems in software development.
We include design patterns in a discussion of software development because this is where they tend to be applied: they're more detailed that architecture styles, but more abstract than source code. Often you will find that when you are working through high-level design, and describing the problem to address, you will recognize a problem as similar to something else that you've encountered. A design pattern is a way to formalize that idea of a common, reusable solution, and give you a standard terminology to use when discussing this design with your peers.
Patterns originated with Christopher Alexander, an architect, in 1977 [Alexandar 1977]. Design patterns in software gained popularity with the book Design Patterns: Elements of Reusable Object-Oriented Software, published in 1994 [Gamma 1994]. There have been many books and articles published since then, and during the early 2000s there was a strong push to expand Design Patterns and promote their use.
Design patterns have seen mixed success. Some criticisms levelled:
- They are not comprehensive and do not reflect all styles of software or all problems encountered.
- They are old-fashioned and do not reflect current software practices.
- They add flexibility, at the cost of increased code complexity.
Broad criticisms are likely unfair. While it's true that not all patterns are used, many of them are commonly used in professional practice, and new patterns continue to be identified. Design patterns certainly can add complexity to code, but they also encourage designs that can help avoid subtle bugs later on.
In this section, we'll outline the more common patterns, and indicate where they may be useful. The original set of patterns were subdivided based on the types of problems they addressed.
We'll examine a number of the patterns below.
The original patterns and categories are taken from Eric Gamma et al. 1994. Design Patterns: Elements of Reusable Object-Oriented Software.
Examples and some explanations are from Alexander Shvets. 2019. Dive Into Design Patterns (link).
#
Creational Patterns
Creational Patterns control the dynamic creation of objects.
#
Example: Builder Pattern
Builder
is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
Imagine that you have a class with a large number of variables that need to be specified when it is created. e.g. a house class, where you might have 15-20 different parameters to take into account, like style, floors, rooms, and so on. How would you model this?
You could create a single class to do this, but you would then need a huge constructor to take into account all the different parameters.
- You would then need to either provide a long parameter list, or call other methods to help set it up after it was instantiated (in which case you have construction code scattered around).
- You could create subclasses, but then you have a potentially huge number of subclasses, some of which you may not actually use.
The builder pattern suggests that you put the object construction code into separate objects called builders. The pattern organizes construction into a series of steps. After calling the constructor, you call methods to invoke the steps in the correct order (and the object prevents calls until it is constructed). You only call the steps that you require, which are relevant to what you are building.
Even if you never utilize the Builder pattern directly, it's used in a lot of complex Kotlin and Android libraries. e.g. the Alert dialogs in Android.
val dialog = AlertDialog.Builder(this)
.setTitle("Title")
.setIcon(R.mipmap.ic_launcher)
.show()
#
Example: Singleton
Singleton
is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
Why is this pattern useful?
- Ensure that a class has just a single instance. The most common reason for this is to control access to some shared resource—for example, a database or a file.
- Provide a global access point to that instance. Just like a global variable, the Singleton pattern lets you access some object from anywhere in the program. However, it also protects that instance from being overwritten by other code.
All implementations of the Singleton have these two steps in common:
- Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
- Create a static creation method that acts as a constructor.
In languages like Java, you would express the implementation in this way:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
In Kotlin, it's significantly easier.
object Singleton{
init {
println("Singleton class invoked.")
}
fun print(){
println("Print method called")
}
}
fun main(args: Array<String>) {
Singleton.print()
// echos "Print method called" to the screen
}
The object
keyword in Kotlin creates an instance of a generic class., i.e. it's instantiated automatically. Like any other class, you can add properties and methods if you wish.
Singletons are useful for times when you want a single, easily accessible instance of a class. e.g. Database object to access your database, Configuration object to store runtime parameters, and so on. You should also consider it instead of extensively using global variables.
#
Structural Patterns
Structural Patterns are about organizing classes to form new structures.
#
Example: Adapter
Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
Imagine that you have a data source in XML, but you want to use a charting library that only consumes JSON data. You could try and extend one of those libraries to work with a different type of data, but that's risky and may not even be possible if it's a third-party library.
An adapter is an intermediate component that converts from one interface to another. In this case, it could handle the complexities of converting data between formats. Here's a great example from Shvets (2019):
The simplest way to implement this is using object composition: the adapter is a class that exposes an interface to the main application (client). The client makes calls using that interface, and the adapter performs necessary actions through the service (which is often a library, or something whose interface you cannot control).
- The client is the class containing business logic (i.e. an application class that you control).
- The client interface describes the interface that you have designed for your application to communicate with that class.
- The service is some useful library or service (typically which is closed to you), which you want to leverage.
- The adapter is the class that you create to serve as an intermediary between these interfaces.
- The client application isn't coupled to the adapter because it works through the client interface.
#
Behavioural Patterns
Behavioural Patterns are about identifying common communication patterns between objects.
#
Example: Command
Command
is a behavioural design pattern that turns a request into a stand-alone object that contains all information about the request (a command could also be thought of as an action to perform).
Imagine that you are writing a user interface, and you want to support a common action like Save. You might invoke Save from the menu, or a toolbar, or a button. Where do you put the code that actually handles saving the data?
If you attach it to the object that the user is interacting with, then you risk duplicating the code. e.g.
The Command
pattern suggests that you encapsulate the details of the command that you want executed into a separate request, which is then sent to the business logic layer of the application to process.
The command class relationship to other classes:
#
Example: Observer (MVC)
Observer
is a behavioural design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing. This is also called publish-subscribe.
The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the changes to its state, we’ll call it publisher. All other objects that want to track changes to the publisher’s state are called subscribers, or observers of the state of the publisher.
Subscribers register their interest in the subject, who adds them to an internal subscriber list. When something interest happens, the publisher notifies the subscribers through a provided interface.
The subscribers can then react to the changes.
A modified version of Observer is the Model-View-Controller (MVC) pattern, which puts a third intermediate layer—the Controller—between the Publisher and Subscriber to handle user input. We'l review this further in Building Applications > User Interfaces.