
Содержание:
Часть 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, если, конечно, надумаете его использовать в вашем приложении. :)
Комментарии (6)

dzzh
13.02.2017 00:53+1А на вот этот репозиторий вы смотрели? Или принципиально хочется сделать самому?

s_suhanov
13.02.2017 14:05Вот теперь, благодаря вам, смотрел.
К сожалению в нем есть два недостатка:
Отсутствует объяснение что к чему в каждом паттерне. Ведь не всегда, глядя на определенное количество классов/протоколов, можно понять что с чем и как взаимодействует. И самое главное — почему именно так.
- Примеры только на Swift, а я в своих статья разбираюсь еще и с Objective-C.
 

PavelGatilov
16.02.2017 14:23+1Спасибо за статью, но есть замечание:
Зачем все примеры по паттернам в упор не используют реальные примеры? Зацепите реальный пример с реальной привязкой к экранам, без этого выглядит как в анекдоте: «2+2 = 4, понятно? теперь решите уравнение 3x^2 + 5x + 1 = 0»
s_suhanov
16.02.2017 14:24Согласен с вами на 100%. Но я эту серию постов пишу больше для того, чтоб разобраться с общим принципом работы каждого паттерна и синтаксисом обоих языков.
          
 
reforms
Спасибо за статью. Я не писал ни на Objective-C и ни на Swift, но примеры для Swift смотрятся так, как будто это твой язык разработки.
s_suhanov
Спасибо, что читаете. :)
Да, Swift для меня все-таки ближе, на Objective-C удается покодить редко, к сожалению. :(