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 (link).

Examples and some explanations are from Alexander Shvets. 2019. Dive Into Design Patterns (link).

Creational Patterns

Creational Patterns control the dynamic creation of objects.

PatternDescription
Abstract FactoryProvide an interface for creating families of related or dependent objects without specifying their concrete classes.
BuilderSeparate the construction of a complex object from its representation, allowing the same construction process to create various representations.
Factory MethodProvide an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
PrototypeSpecify the kinds of objects to create using a prototypical instance, and create new objects from the ‘skeleton’ of an existing object, thus boosting performance and keeping memory footprints to a minimum.
SingletonEnsure a class has only one instance, and provide a global point of access to it.

Builder

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.

Builder pattern

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()

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?

  1. 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.
  2. 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:

  1. Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
  2. 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.

Factory Method

The Factory Method is intended to solve the issue of not knowing which subclass to create (i.e. need to defer to runtime, often because of runtime conditions).

The Factory Method defines a way to decide which subclasses to instantiate by defining method whose role is to examine conditions and make that decision for the caller. i.e. instantiation is deferred to a Factory Method.

How to use it?

  1. Create a class hierarchy for the classes that you need to instantiate, including a base class (or interface) and all subclasses.
  2. Create a Factory class that instantiates and returns the correct subclass.

Here’s an example of using the Factory Method pattern to read the pieces of a chess board from a data file, and instantiate each object as it’s read.

sealed class Piece(val position: String)                          // base class
class Pawn(position: String) : Piece(position)                    // derived classes
class Queen(position: String) : Piece(position)

fun generatePieces(notation: List<String>): List<Piece> {         // factory method
    return notation.map { piece ->
        val pieceType = piece.get(0)
        val position = piece.drop(1)
        when(pieceType) {
            'p' -> Pawn(position)
            'q' -> Queen(position)
            else -> error("Unknown piece")
        }
    }
}

val notation = listOf("pa3", “qc5")                               // method returns list of pieces
val list = generatePieces(notation)

Structural Patterns

Structural Patterns are about organizing classes to form new structures.

PatternDescription
Adapter, WrapperConvert the interface of a class into another interface clients expect. An adapter lets classes work together that could not otherwise because of incompatible interfaces.
BridgeDecouple an abstraction from its implementation allowing the two to vary independently.
CompositeCompose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
DecoratorAttach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
ProxyProvide a surrogate or placeholder for another object to control access to it.

Decorator

The decorator pattern allows you to describe combinations of parameters or classes, without the complexity of having a subclass for each combination.

A decorator allows you to chain together the classes that you want to process a request.

e.g. You are building a message notifier, which allows your application to send out messages.

It’s easy to see how to create a specialized notifier, but what if you want to have a message that is sent to multiple notifiers at the same time? This would be messy as a set of classes, but simpler to compose using a decorator.

Decorator pattern

Example of a decorator being used.

fun main() {
    val logger = Logger()
    val cache = Cache()
    val request = Request("http://example.com")
    val response = processRequest(request, logger, cache)
    println("Results: ${response}")
}

// what if I don’t want all of these processors to run?
fun processRequest(request: Request, logger: Logger, cache: Cache): Response {
    logger.log(request.toString())
    val cached = cache.get(request) ?: run {
        val response = Response("You called ${request.endpoint}")
        cache.put(request, response)
        response
    }
    return cached
}

fun main() {
   val request = Request("http://example.com")
 val processor: Processor = LoggingProcessor(Logger, RequestProcessor()))
   println("Results: ${processor.process(request)}")
}

interface Processor { fun process(request: Request): Response }

class LoggingProcessor(val logger: Logger, val processor: Processor) : Processor {
    override fun process(request: Request): Response {
        logger.log(request.toString())                      // do appropriate work
        return processor.process(request)                   // pass to next Processor
    }
}

class RequestProcessor(): Processor {
    override fun process(request: Request): Response {
        return Response("You called ${request.endpoint}”)   // do appropriate work
    }
}

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):

Adapter converting between data formats

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).

Adapter pattern

  1. The client is the class containing business logic (i.e. an application class that you control).
  2. The client interface describes the interface that you have designed for your application to communicate with that class.
  3. The service is some useful library or service (typically which is closed to you), which you want to leverage.
  4. The adapter is the class that you create to serve as an intermediary between these interfaces.
  5. 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.

PatternDescription
CommandEncapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also allows for the support of undoable operations.
IteratorProvide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
MementoWithout violating encapsulation, capture and externalize an object’s internal state allowing the object to be restored to this state later.
ObserverDefine a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically.
StrategyDefine a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
VisitorRepresent an operation to be performed on the elements of an object structure. Visitor lets a new operation be defined without changing the classes of the elements on which it operates.

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.

Different objects want to invoke the same code

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.

Command request

The command class relationship to other classes:

Command pattern implementation

Strategy

The strategy pattern is a way to swap algorithms at runtime. It’s often modelled as a set of interchangeable classes.

Why is this pattern useful? It allows you to add new algorithms or modify existing ones without modifying existing code. It can provides extensibility and flexibility to your solution.

How does it work? Extract algorithms into separate classes called strategies. The original class, called context, must have a field for storing a reference to one of the strategies. The context delegates the work to a linked strategy object.

Here’s an example of some UI fields that we want to extend. We’ll use the strategy pattern to do this.

Info

Credit to Dave Leeds for this example.

Here’s a simple example without using this pattern. We have specific specialized classes for each field.

// starting code
interface FormField {
    val name: String
    val value: String
    fun isValid(): Boolean
}

class EmailField(override val value: String) : FormField {
    override val name = "email"
    override fun isValid(): Boolean {
        return value.contains("@") && value.contains(".")
    }
}

class UsernameField(override val value: String) : FormField {
    override val name = "username"
    override fun isValid(): Boolean {
        return value.isNotEmpty()
    }
}

class PasswordField(override val value: String) : FormField {
    override val name = "password"
    override fun isValid(): Boolean {
        return value.length >= 8
    }
}

fun main() {
    val emailForm = EmailField("nobody@email.com")
    val usernameForm = UsernameField("none"
    val passwordForm = PasswordField("*")
}

Let’s refactor this to use the pattern. Our validator interface/classes represent the interchangeable strategies.

// ending code
fun interface Validator {
    fun isValid(value: String): Boolean
}

val emailValidator = Validator { it.contains("@") && it.contains(".") }
val usernameValidator = Validator { it.isNotEmpty() }
val passwordValidator = Validator { it.length >= 8 }

class FormField(val name: String, val value: String, private val validator: Validator) {
    fun isValid(): Boolean {
        return validator.isValid(value)
    }
}

fun main() {
    val emailForm = FormField("email", "nobody@email.com", emailValidator)
    val usernameForm = FormField("username", "empty", usernameValidator)
    val passwordForm = FormField("email", "***", passwordValidator)
}

Observer

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.

Observer

Observer subscribers being notified

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 User Interfaces.