Part 0. Singleton Single
Part 1. Strategy
Part 2. The Observer
Let me remind you that in this series of articles, I am analyzing the book "Design Patterns" by Eric and Elizabeth Freeman. And today we will study the pattern "Strategy". Go.
The authors of the book tell us the story of the creation of the SimUDuck application. Let's start with the implementation of the initial state of the application: we have the abstract Duck
class and two of its heirs: MallardDuck
and RedheadDuck
. Immediately we are confronted with the first difficulty: there are no abstract classes in Objective-C and Swift.
We get out of the situation with those tools that are: for Objective-C we declare the usual Duck
class (in it will be the default implementations) and add the AbstractDuck
protocol to it (in it there will be abstract methods that must be implemented in the heirs). It looks like this:
// Objective-C @protocol AbstractDuck <NSObject> - (void)display; @end @interface Duck : NSObject - (void)quack; - (void)swim; @end
Accordingly, the heirs will be as follows:
// Objective-C @interface MallardDuck : Duck <AbstractDuck> @end @implementation MallardDuck - (void)display { } @end @interface RedheadDuck : Duck <AbstractDuck> @end @implementation RedheadDuck - (void)display { } @end
In Swift, this is a little easier: the protocol and its extensions are sufficient (in the extension, you can implement some protocol methods by default)
// Swift protocol Duck { func quack() func swim() func display() } extension Duck { func quack() { } func swim() { } }
And the heirs:
// Swift class MallardDuck: Duck { func display() { } } class RedheadDuck: Duck { func display() { } }
For this, the corresponding method appears in the Duck
parent class. And soon after that, it turns out that there is another heir - RubberDuck
. Rubber ducks do not fly, and since the method is added to the parent class, it will be available for rubber ducks. In general: the situation was not simple. With further expansion of the application, there will be difficulties with the support of the flight functions (and not only with it, with the quacking function the same story) and with other types of ducks (wood, for example).
First, the authors of the book propose to solve the problem by putting the functions of flight and quacking into separate interfaces (for Objective-c and Swift-protocols) Flyable
and Quackable
. But this option is not as good as it seems at first glance. The slightest change in the flight function, which must be applied to all flying ducks, entails the introduction of the same code in many places of the program. So this solution is definitely not suitable.
(referring to the unsuitability of this option, the authors refer to the fact that in interfaces (for us protocols) there are no default implementations, but this is true only for Objective-C, but in Swift the default implementation for flight and quacking could be written in extensions of these protocols and redefine these functions only where necessary and not everywhere)
Well, and besides, one of the main goals of the pattern is to replace the implementation of behaviors at runtime, so the authors propose to make the implementation of behaviors out of the Duck
class.
To do this, create the protocols FlyBehavior
and QuackBehavior
:
// Objective-C @protocol FlyBehavior <NSObject> - (void)fly; @end @protocol QuackBehavior <NSObject> - (void)quack; @end
// Swift protocol FlyBehavior { func fly() } protocol QuackBehavior { func quack() }
And specific classes that implement these protocols: FlyWithWings
and FlyNoWay
for FlyBehavior
, as well as Quack
, Squeak
and MuteQuack
for QuackBehavior
(I'll give an example for FlyWithWings
, the rest are implemented in a very similar way):
// Objective-C @interface FlyWithWings : NSObject <FlyBehavior> @end @implementation FlyWithWings - (void)fly { // fly implementation } @end
// Swift class FlyWithWings: FlyBehavior { func fly() { // fly implementation } }
Now we, in fact, delegate our behavior to any other class that implements the corresponding interface (protocol). How to tell our duck what its behavior in flight and with quacking should be? Very simply, we add to our class (in Swift - protocol) Duck
two properties:
// Objective-C @property (strong, nonatomic) id<FlyBehavior> flyBehavior; @property (strong, nonatomic) id<QuackBehavior> quackBehavior;
// Swift var flyBehavior: FlyBehavior { get set } var quackBehavior: QuackBehavior { get set }
As you can see, they do not have a specific type defined; it is only determined that this is a class that is signed for the corresponding protocol.
quack
fly
and quack
our parent class (or protocol) Duck
with similar ones:
// Objective-C - (void)performFly { [self.flyBehavior fly]; } - (void)performQuack { [self.quackBehavior quack]; }
// Swift func performFly() { flyBehavior.fly() } func performQuack() { quackBehavior.quack() }
Now our duck simply delegates its behavior to the corresponding behavioral object. How do we set the behavior of each duck? For example, during initialization (example for MallardDuck
):
// Objective-C - (instancetype)init { self = [super init]; if (self) { self.flyBehavior = [[FlyWithWings alloc] init]; self.quackBehavior = [[Quack alloc] init]; } return self; }
// Swift init() { self.flyBehavior = FlyWithWings() self.quackBehavior = Quack() }
Our pattern is ready :)
In iOS development, the Strategy pattern can be found, for example, in the MVP architecture: in it, the presenter is nothing more than a behavioral object for the view controller (the view controller, as you remember, only informs the presenter about the user's actions, but the logic data processing is determined by the presenter), and vice versa: the controller view is a behavioral object for the presenter (the presenter only says "show data to the user", but how it will be shown is decided by the controller view). You will also find this pattern in VIPER, if, of course, you decide to use it in your application. :)
Source: https://habr.com/ru/post/321182/