A cooking master: Strategy Pattern and Swift

Ario Liyan
10 min readOct 8, 2022

--

In this article, you are going to take a deep dive into the Strategy pattern. I wrote this article after reading three books(Design patterns: elements of reusable object-oriented software, Head First Design Pattern, and Dive Into Design Patterns) watching a couple of Youtube videos, and reading many other articles. This article is written with Swift language in mind. This post is intended to be used as a complete reference. Good read.

From Dive into design patterns by Alexander Shevts

Table of contents

  • Description
  • Implementation
  • When to use the strategy pattern?
  • Cons

Description

  • Preface
  • Definition
  • Analogy
  • Extra Notes

Preface

We have many scenarios in Object-oriented programming in which we need a superclass with different child classes inherited from. Sometimes the child classes (or inherited classes) are similar to each other with minor differences that we can’t have a single class to define both but they are very similar to each other for example we want to have a Vehicle superclass and fighter jet and passenger airplane child classes. The fighter jet and passenger plane classes are very similar to each other, they both fly and … but at the same time, we can’t have them as a single class.

So normally we define two different classes in which some codes are repeated in both of them…

Right now we have broken the DRY principle.

Let’s say later in the project we were to implement another class for cargo planes, again we need to implement a whole new class …

We can clearly see that we are treading on a dangerous path of repeating ourselves.

So what should we do then?

Let’s take a look at the Strategy pattern definition

Definition:

The strategy pattern Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Chapter 1, On page 24 Head first design pattern 2020 Edition.

So let’s talk about it:
It is saying that instead of defining different classes for different concepts we can have a single class and different algorithms, which we can configure the class with those algorithms to define different concepts(we’ll read more about it).

So you have a family of algorithms that are interchangeable and you can use one instead of another, we decoupled the algorithms from the one, it uses the algorithms which means for using different algorithms your client(the one that uses the algorithm) doesn’t need to change itself in order to use the algorithm.

Actually, it is claiming that with the strategy pattern you can plug and play different functionalities into a class.

For example: Consider a list or an array that the implementation of the sort function is built in the List class so you can’t change the sorting algorithm based on your needs but if you use the strategy pattern and inject the sort function then the sort function can vary independently based on different types or whatever you need without needing to change the List or the array class.

Analogy

UML Diagram of Inheritance

Inheritance
The vehicle is a superclass that the ship class and car class both inherit from it, the vehicle has two main methods Move() and Horn() both are implemented according to the needs of the child class within the child class (with overriding the parent method) or the child class can use the superclass implementation. This is how we use inheritance for code reuse.

What is the problem then?
The problem is in the essence of change. Most often we face systems whose requirements change over time so our implementations need to change too and then our current design may not necessarily be appropriate for the coming requirements.

For example, let's say we have a new vehicle called “Plane”, the plane inherits both the Move() and Horn() method but a plane doesn’t have a horn to Horn() 🥸 so it shouldn’t have a method for that.

The related example in Swift is subclassing UIView not all UIView’s subclasses need to inherit all the methods and fields but it’s inevitable.

It might occur to you to say: so let’s implement the Horn() method as a method that does nothing. In response, I also can argue that not being able to horn is kind of a horning behavior(we get more into this later). But yea you are right this could actually do fine(you may feel that this perspective about changes, pushes us to tread on a dangerous path).

Let’s say we have another vehicle, we have a Bicycle class that has a specific implementation for its Moving() method, it moves on two wheels so we need to add a new implementation. But later on, we need to add another Vehicle and that is a Motorbike class that has a specific implementation for its Moving() method, too. Its methods vary from the Ship class and the car class but it’s the same as the Bicycle class. Now we have positioned ourselves where we should copy the Bicycle’s implementation of the Move() method for our Motorbike class. Now we are repeating ourselves(100% duplication).

So even now, someone can argue that we can implement another class for the specific two wheels Move() method …. Maybe this wouldn’t be a problem for some languages like C++ which support multi-inheritance but swift doesn’t support it, so this is our dead end in swift language we can’t do anything except copy and pasting the method.

But for the sake of programming knowledge just for a moment let’s imagine that swift does support multi-inheritance what’s then?

So the answer is this can expand to lots of other methods for example Fuel() method which is the same for the Car class and Motorbike class but it is different for all the others…

Now the most salient thing is that we are arguing for pushing something crazy, we are forcing our system to be the Frankenstein monster 🧟.

Let’s stop this madness right here and right now let’s use the strategy pattern.

Extra Note

Inheritance tree

In inheritance as long as the behaviors are shared downward everything is fine, but as soon as you start to share behaviors horizontally there is no way unless you get yourself into the tangling path of multi-inheritance.

As Sandi Metz (author of Practical Object Oriented Design in Ruby and 99 Bottles of OOP) said

The solution to the problems with inheritance is not more inheritance.

She argues that composition should often be favored over inheritance.

Implementation

  • Preface
  • Composition

Preface

Let’s revise the strategy pattern definition

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Gang of four: Design pattern

This is exactly pointing to our problem.

Our clients here are our classes: Car, Motorbike, Plane, etc.

Our strategies are what we call protocols in swift and interface in many other languages that define the functionalities.

So we need to define a protocol for each method(such as Move and Horn).

The Strategy pattern suggests that you take a class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies.

The original class is called context, and must have a field for storing a reference to one of the strategies. The context delegates the work to a linked strategy object instead of executing it on its own.

The context isn’t responsible for selecting an appropriate algorithm for the job. Instead, the client passes the desired strategy to the context. In fact, the context doesn’t know much about strategies. It works with all strategies through the same generic interface, which only exposes a single method for triggering the algorithm encapsulated within the selected strategy. This way the context becomes independent of concrete strategies, so you can add new algorithms or modify existing ones without changing the code of the context or other strategies.

Remember:
It’s about using composition rather than inheritance, It’s about understanding that inheritance is not intended for code reuse.

Composition

UML Diagram: Composition

Let’s see some code:

Defining Strategies

Here we’ve defined the global functionalities that we wish our vehicles to have.

Explicit implementation of different families of algorithms

As it was mentioned before we need explicit implementations for each behavior based on our needs we may want to have two or more algorithms for one behavior such as moving.

Above I used both struct and class to showcase that you can use both of them based on your need but if you decide to use class you’ll be better off marking it “final”, to tell the compiler that you are not going to use this class in inheritance(a performance consideration).

Vehicle Class

For defining the class, we have three basic steps:
Firstly we need to define variables of the behavior that we desire(their type should be the same as the related protocol)

Secondly, we need to implement a proper initializer for our class as shown above.

Thirdly, for each behavior, we should define a public function to run the desired behavior.

In the code below, as the last step, we need to configure different objects based on our needs. I mean before with inheritance, we initialized different objects from different classes but here we define different objects from the same class, only configuring them with the different algorithms that we want. For example, a car object has a horn, is filled with petrol, and move with 4 wheels, so in the initializing step, we inject the related algorithms(here we wrapped the algorithm in a class and we actually are injecting the classes).

As you can see we have different objects of a single class which are configured to be a car, motorbike, etc.

Now you can see that we need inheritance so much less than some people make us believe(It’s Illuminati, they are trying to push their agenda 🥴).

There are a couple of important things to consider:

  • We don’t want to use inheritance, so use the “final” keyboard to make your classes unable to be inherited from.
  • We can use struct instead of class.
  • In the main class (in our example The Vehicle class) we don’t want to have concrete implementations for our methods so we use injection so we can configure different settings for our object.
  • For defining the configurable behaviors it’s better to define private attributes and then use them to call the injected implementations in a public method within our main class.
  • When you are using the strategy pattern(also in programming in general) don’t underestimate the power of conventional naming, naming your classes and objects is the key to better understanding the concepts both for you in the future and for other developers who work with you on the same source code(be consistent with naming).
  • If reusing the code is not a consideration so there is no need for using the strategy pattern.
  • The relationship in strategy pattern is of type “has a” rather than “is a” (which is used in inheritance — They are UML relations)
  • In the strategy pattern now suddenly we are facing different configurations of the same class that makes different objects of the same type.
  • All of this is only possible by injecting the behaviors rather than hard-coding them within the class.

so to recap everything

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

When to use the strategy pattern?

  • Use the Strategy pattern when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime.
  • Use the Strategy when you have a lot of similar classes that only differ in the way they execute some behavior.
  • Use the pattern to isolate the business logic of a class from the implementation details of algorithms that may not be as important in the context of that logic.
  • Use the pattern when your class has a massive conditional operator that switches between different variants of the same algorithm.

Cons

There are also a couple of cons to it

  • If you only have a couple of algorithms and they rarely change, there’s no real reason to overcomplicate the program with new classes and interfaces that come along with the pattern.
  • Clients must be aware of the differences between strategies to be able to select a proper one.
  • A lot of modern programming languages like our beloved swift have functional type support that lets you implement different versions of an algorithm inside a set of closures (anonymous functions). Then you could use these functions exactly as you’d have used the strategy objects, but without bloating your code with extra classes and interfaces.

--

--

Ario Liyan

As an iOS developer with a passion for programming concepts. I love sharing my latest discoveries with others and sparking conversations about technology.