I hope you're still with us; now, we will introduce you to the third and last category of the design patterns, which is categorized as a Gang of Four (GoF) design patterns: the behavioral pattern. Behavioral patterns are dedicated to algorithms and communication between them.
As algorithms consist of several operations that are divided into different classes, behavioral patterns can handle the organization of such classes and the ways in which they can communicate with one another.
The behavioral category contains 11 patterns that we will discuss through four chapters. In this chapter, we will discuss the following three patterns:
When you need to change part of an object's algorithm at runtime, without modifying the client, the strategy pattern is the appropriate pattern to be used.
It removes an algorithm from its host class and moves it to a separate class. The algorithm part that can change is the strategy. Every strategy uses the same interface. The class using the strategy pattern delegates the treatment of the algorithm to the strategy.
The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at runtime.
The algorithm changes don't affect the client part. This pattern can be used in the following cases:
if
conditions instruction complexify the codeThe participants in the strategy pattern are as follows:
IStrategy
: This class defines the common interface implemented by all algorithms. This is the interface used by the ClassUsingStrategy
class to invoke the right algorithm.ConcreteStrategyA
and ConcreteStrategyB
: These are concrete classes that implement different algorithms based on the IStrategy
interface.ClassUsingStrategyA
and ClassUsingStrategyB
: These are classes that use an algorithm from classes that implement the IStrategy
interface. These classes have a reference to one instance of one of the concrete strategy classes. These classes can expose some internal data to the implementation classes.The
ClassUsingStrategy
and ConcreteStrategy
classes interact to implement algorithms.
In most cases, data needed by the algorithms is sent as arguments to the constructor but can be sent by a set property too. If needed, the ClassUsingStrategy
class can provide you some methods to allow you to access its internal data.
The Client
instance will initiate ClassUsingStrategy
with a Strategy
object and call the ClassUsingStrategy
method that uses the strategy pattern.
Then, this class will send the request received from the client to the instance that is referenced by the strategy attribute.
We will see how to implement the strategy pattern using a simple example.
Some objects can move, but some of them don't move in the same manner. Each object has a particular manner in which it moves: some can walk and others can run and fly. The move behavior is our strategy, and we will encapsulate the performMove()
action in the concrete strategy class that is referenced in the concrete class using the strategy.
Open the strategy pattern Xcode project to see the organization of our code. The participants in this pattern are separated in four folders, as shown in the following screenshot:
The first thing that we will do is define the interface of the move strategy and then describe AbstractClassUsingMoveStrategy
.
The interface of the move strategy is really easy; we will only tell the class implementing the strategy that we are waiting for a performMove
method.
The MoveStrategyProtocol.swift
file is as follows:
//Common Interface used by algorithms protocol MoveStrategyProtocol { func performMove() }
Our abstract class needs to have a role to keep a reference to the strategy that will be applied by encapsulating the call to the performMove
method of the current strategy into its own method that we will call move()
. The instance of the strategy object will be received in the constructor of the class implementing the AbstractObjectThatMove
class.
For our demo, we will add an internal computed property, WhoAmI
, where we will set or get the name of the concrete class using the strategy pattern.
Our AbstractObjectThatMove.swift
file is as follows:
class AbstractObjectThatMove { private var strategy: MoveStrategyProtocol! private var whoAmI:String = "Unknown Object" required init(strategy: MoveStrategyProtocol) { self.strategy = strategy } func move(){ strategy.performMove() } internal var WhoAmi: String { get { return whoAmI } set { whoAmI = newValue } } }
Swift 2.0 has no support for abstract classes as yet. Here, we named the AbstractObjectThatMove
class, even if it is not a "real" abstract class, only to be as close as possible to the general concept of the pattern. Nevertheless, there might be a way to have something that seems like an abstract class, but it differs from the general concept of the pattern where we pass a strategy to the initializer. Swift 2.0 has protocol extensions that give the opportunity to add partial implemented methods to protocols, and in "some way", make an abstract class:
protocol AbstractObjectThatMove { var WhoAmi: String { get set} } extension AbstractObjectThatMove { func move(strategy: IMoveStrategy) { strategy.performMove() } } class Human : AbstractObjectThatMove { var WhoAmi: String = "i'm a human" }
Then, we can process the following code:
print("- *** working with Human") let strategyForHuman = WalkMoveStrategy() let human = Human() // Tell who am I print(human.WhoAmi) //perform human move: human.move(strategyForHuman)
So, we come back to the implementation of the pattern. In our current example, we have three concrete classes that implement the abstract classes: Rabbit
, Bird
, and Human
. As the implementation is the same for all these three objects, I will only display the implementation of the concrete Human
class:
class Human: AbstractObjectThatMove { required init(strategy: MoveStrategyProtocol){ super.init(strategy: strategy) self.WhoAmi = "i'm a human" } }
We want to add a value to the WhoAmI
property while it is initializing, so we inform the init
method that this method is required. We then set the value of the property with a simple self.WhoAmi = "I'm a human"
statement.
Now, we will implement WalkMoveStrategy
, as defined in the MoveStrategyProtocol
protocol:
class WalkMoveStrategy: MoveStrategyProtocol { func performMove() { print("I am walking") } }
There's nothing complex here; we implement the performMove
method and print the message of the move action. The RunMoveStrategy
and FlyMoveStrategy
methods are implemented in the same way; just the print
statement changes:
Last, to complete the example, we will make our human, bird, and rabbit perform a move according to the strategy they apply:print("- *** working with Human") let strategyForHuman = WalkMoveStrategy() let human = Human(strategy: strategyForHuman) // Tell who am I print(human.WhoAmi) //perform human move: human.move() print("- *** working with Bird") let strategyForBid = FlyMoveStrategy() let bird = Bird(strategy: strategyForBid) // Tell who am I print(bird.WhoAmi) //perform human move: bird.move() print("- *** working with Rabbit") let strategyForRabbit = RunMoveStrategy() let rabbit = Rabbit(strategy: strategyForRabbit) // Tell who am I print(rabbit.WhoAmi) //perform human move: rabbit.move()
In the preceding highlighted code, you can see how we proceeded to apply the strategy to the Human
class using the following steps:
performMove
method of the strategy that the concrete class has in reference.You should see the following result in the console of Xcode: