Патерни проектування, погляд iOS розробника. Частина 1. Стратегія


Зміст:
Частина 0. Сінглтон-Одинак
Частина 1. Стратегія
Нагадаю, що в цій серії статей, я розбираю книгу "Патерни проектування" Еріка і Елізабет Фрімен. І сьогодні ми вивчимо патерн "Стратегія". Поїхали.
Звідки ростуть ноги (і крила)
Автори книги розповідають нам історію про створення програми SimUDuck. Почнемо з реалізації початкового стану додатки: у нас є абстрактний клас
Duck
і два його спадкоємця:
MallardDuck
та
RedheadDuck
. Тут же ми стикаємося з першою складністю: в Objective-C і Swift немає абстрактних класів.
Виходимо з ситуації тими інструментами, що є: для Objective-C оголошуємо звичайний клас
Duck
(в ньому будуть реалізації за замовчуванням) і до нього додаємо протокол
AbstractDuck
(в ньому будуть абстрактні методи, які потрібно реалізувати спадкоємців). Виглядає це так:
// Objective-C
@protocol AbstractDuck <NSObject>

- (void)display;

@end

@interface Duck : NSObject

- (void)quack;
- (void)swim;

@end

Відповідно спадкоємці будуть такими:
// Objective-C
@interface MallardDuck : Duck <AbstractDuck>

@end

@implementation MallardDuck

- (void)display {

}

@end

@interface RedheadDuck : Duck <AbstractDuck>

@end

@implementation RedheadDuck

- (void)display {

}

@end

В Swift це зробити трохи простіше: достатньо протоколу і його розширення (в розширенні можна деякі методи протоколу реалізувати за замовчуванням):
// Swift
protocol Duck {
func quack()
func swim()
func display()
}

extension Duck {

func quack() {

}

func swim() {

}

}

І спадкоємці:
// Swift
class MallardDuck: Duck {

func display() {

}

}

class RedheadDuck: Duck {

func display() {

}

}

Додаток розвивається і у качок з'являється можливість літати
Для цього відповідний метод з'являється у батьківському класі
Duck
. І незабаром після цього з'ясовується, що є ще один спадкоємець —
RubberDuck
. Гумові качки адже не літають, а оскільки метод доданий в батьківський клас, то він буде доступний і для гумових качок. Загалом: ситуація виявилася не з простих. При подальшому розширенні програми будуть виникати складнощі з підтримкою функцій польоту (і не тільки з нею, з функцією кряканья та ж історія) і з іншими видами качок (дерев'яних, наприклад).
Спочатку автори книги пропонують вирішувати проблему винесенням функцій польоту і кряканья в окремі інтерфейси (для Objective-c і Swift — протоколи)
Flyable
та
Quackable
. Але цей варіант виявляється зовсім не такий хороший, яким здається на перший погляд. Найменша зміна функції польоту, яке має бути застосоване до всіх літаючим качкам тягне за собою внесення одного і того ж коду в багатьох місцях програми. Так що таке рішення однозначно не підходить.
(кажучи про непридатність цього варіанту, автори посилаються на те, що в інтерфейсах (для нас протоколах) немає реалізацій за замовчуванням, але це справедливо лише для Objective-C, а ось в Swift реалізації за замовчуванням для польоту і кряканья можна було б написати в розширеннях цих протоколів та ігнорувати ці функції тільки там де потрібно, а не скрізь)
Ну і до того ж, одна з головних цілей патерну — подменяемая реалізація поведінки під час виконання, тому автори пропонують виносити реалізації поводжень з класу
Duck
.
Для цього створимо протоколи
FlyBehavior
та
QuackBehavior
:
// Objective-C
@protocol FlyBehavior <NSObject>

- (void)fly;

@end

@protocol QuackBehavior <NSObject>

- (void)quack;

@end

// Swift
protocol FlyBehavior {
func fly()
}

protocol QuackBehavior {
func quack()
}

І конкретні класи реалізують ці протоколи:
FlyWithWings
та
FlyNoWay
,
FlyBehavior
, а також
Quack
,
Squeak
та
MuteQuack
,
QuackBehavior
(наведу приклад для
FlyWithWings
, інші реалізуються дуже схожим чином) :
// Objective-C
@interface FlyWithWings : NSObject <FlyBehavior>

@end

@implementation FlyWithWings

- (void)fly {

// fly implementation

}

@end

// Swift
class FlyWithWings: FlyBehavior {

func fly() {

// fly implementation

}

}

Делегування наше все
Тепер ми, по суті, делегуємо нашу поведінку будь-якого іншого класу, який реалізує відповідний інтерфейс (протокол). Як сказати нашої качки яким має бути її поведінку в польоті і при кряканьи? Дуже просто, додаємо в наш клас (Swift — протокол)
Duck
дві властивості:
// 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 }

Як бачите у них не визначений конкретний тип, визначено лише, що це клас підписаний відповідний протокол.
Методи
fly
та
quack
нашого батьківського класу (чи протоколу)
Duck
замінимо аналогічними:
// Objective-C
- (void)performFly {
[self.flyBehavior fly];
}

- (void)performQuack {
[self.quackBehavior quack];
}

// Swift
func performFly() {
flyBehavior.fly()
}

func performQuack() {
quackBehavior.quack()
}

Тепер наша качка просто делегує свою поведінку відповідним поведінковому об'єкту. Як ми встановлюємо поведінка кожної качці? Наприклад, при ініціалізації (приклад для
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()
}

Наш патерн готовий :)
Висновок
В iOS розробці патерн "Стратегія" ви можете зустріти, наприклад, в архітектурі MVP: в ній презентер є не чим іншим як поведінковим об'єктом для в'ю-контролера (в'ю-контролер, як ви пам'ятаєте, тільки повідомляє презентеру про дії користувача, а от логіку обробки даних визначає презентер), і навпаки: в'ю-контролер — поведінковий об'єкт для презентера (презентер лише говорить "показати користувачеві дані", але як саме вони будуть показані — вирішить в'ю-контролер). Також цей патерн ви зустрінете і в VIPER, якщо, звичайно, надумаєте його використовувати у вашому додатку. :)
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.