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

f68211c5f1eb459ebfb3aa867710072a.jpg

Содержание:

Часть 0. Синглтон-Одиночка
Часть 1. Стратегия


Напомню, что в этой серии статей, я разбираю книгу «Паттерны проектирования» Эрика и Элизабет Фримен. И сегодня мы изучим паттерн «Стратегия». Поехали.


Откуда растут ноги (и крылья)

Авторы книги рассказывают нам историю о создании приложения SimUDuck. Начнем с реализации начального состояния приложения: у нас есть абстрактный класс Duck и два его наследника: MallardDuck и RedheadDuck. Тут же мы сталкиваемся с первой сложностью: в Objective-C и Swift нет абстрактных классов.


Выходим из ситуации теми инструментами, что есть: для Objective-C объявляем обычный класс Duck (в нем будут реализации по умолчанию) и к нему добавляем протокол AbstractDuck (в нем будут абстрактные методы, которые нужно реализовать в наследниках). Выглядит это так:


// Objective-C
@protocol AbstractDuck 

- (void)display;

@end

@interface Duck : NSObject

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

@end

Соответственно наследники будут такими:


// Objective-C
@interface MallardDuck : Duck 

@end

@implementation MallardDuck

- (void)display {

}

@end

@interface RedheadDuck : Duck 

@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 

- (void)fly;

@end

@protocol QuackBehavior 

- (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 

@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;
@property (strong, nonatomic) id 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)

© Habrahabr.ru