structural design patterns

54
ВАМ ПРИВЕТ

Upload: -

Post on 11-Apr-2017

87 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Structural Design Patterns

ВАМ ПРИВЕТ

Page 2: Structural Design Patterns

Advanced OOP : Структурные паттерныYou are Ready?

Alexander Babenko (HuktoDev)iOS DeveloperImprove Digital

Page 3: Structural Design Patterns

Адаптер (Adapter)

Alternate Name : (Wrapper)

Page 4: Structural Design Patterns

• Нужно использовать класс (например, из другой подсистемы), интерфейс которого несовместим

• Мы создали несколько реализаций с общим интерфейсом, и хотим сделать еще одну, но есть уже классное стороннее решение, и мы хотели бы его использовать без изменения интерфейсов

• Мы хотим сделать более подходящий и удобный интерфейс для объекта без изменения исходного объекта (например, он уже везде используется с исходным интерфейсом).

• Мы хотим отойти к более подходящей терминологии. Или добавить дополнительные контракты на входах и выходах. Или скрыть какие-то технологии. В общем, добавить какой-то дополнительный код

Мотивация

Page 5: Structural Design Patterns

• Классическая реализация предлагает композицию

UML-диаграмма

• Разница с декоратором :• 1) Декоратор предлагает новый декорирующий функционал • 2) Адаптер именно адаптирует

Page 6: Structural Design Patterns

• Адаптер обычно реализуется очень просто• Объем работ по адаптации может быть

разным, в зависимости от степени несовместимости интерфейсов

• Если изменяется адаптируемый класс - изменения нужно будет внести почти наверняка только в адаптер

• Есть еще 2 типа адаптеров :

Результаты и нюансы использования :

Сменный адаптер (динамический)Двусторонний адаптер

Page 7: Structural Design Patterns

• По-другому можно назвать “Каркас для плагинов”• Задача в том, чтобы использовать один и тот же объект для

адаптации разных технологий или реализаций• Адаптер без привязки к конкретному адаптируемому объекту.• Адаптер привязывается только к конкретному протоколу

адаптируемого объекта• Обычно используется в крупных системах, или фреймворках,

когда другому разработчику предоставляется возможность написать плагин (сделать инъекцию собственного кода для исполнения)

Сменный адаптер

Адаптируемый (Плагин)

Ядро плагина (Плагин Handler)

CustomPluginProtocol

PrivatePluggableAdapterProtocol

Page 8: Structural Design Patterns

Плагины :• Есть несколько способов реализации сменных

адаптеров (каркас + набор плагинов)

• Реализация через композицию - это, фактически, то же самое делегирование. Например, кастомный DataSource или Delegate для TableView можно считать плагином

• Можно параметризовать блоками с исполнительным кодом. В этом случае у нас отсутствует адаптируемый объект, мы просто наполняем каркас кодом

• Использование базового класса с абстрактными операциями. Например, мы делаем подкласс UIViewController и переопределяем методы LifeCycle

Page 9: Structural Design Patterns

Пример :

Page 10: Structural Design Patterns

Мост (Bridge)Alternate Name :

(Handle/Body)

Page 11: Structural Design Patterns

• Нужно, чтобы абстракция и реализация были независимы, обладали бы минимальными сведениями друг о друге (чтобы их можно было изменять независимо). Даже в отдельных проектах. Например Core -> Custom Targets

Суть проблемы :

Абстракция Реализация

Некорректное состояние дел :

• Трудно расширять• Появляются баги, так как имеется высокая связность

• В интерфейсах всплывают особенности реализации, из-за чего может быть проблема с переиспользованием, или если детачить какую-то технологию

Page 12: Structural Design Patterns

• Отделяем реализацию от абстракции с помощью паттерна Мост

• Делаем, чтобы реализации зависели только от абстракции в одностороннем порядке (абстракция ничего не знает о реализациях)

Способ решения :

Spoiler:Я выделил 2 различных типа Моста :Мост по типу : Абстракция -> Реализация

Мост по типу : Публичная абстракция -> Приватная абстракцияMARK: Второй вариант - более общий и гибкий случай, но применимыйтолько в самых “запущеных” вариантах!

Page 13: Structural Design Patterns

Абстракция -> Реализация :Конкретная технология

моста

Реализация 1

Реализация 2

Реализация 3

Единая абстракция

Базовый класс семейства приватных объектов

Публичная абстракция (Window на экране)

Конкретный интерфейс абстракции

Базовый/Абстрактный класс

Конкретный подкласс

Публичная абстракция -> Приватная абстракция :

Приватная абстракция (Window на экране iPhone)

Конкретный интерфейс абстракции

Реализация конкретного подкласса в семействе

<WindowProtocol>

Window

AlertWindow

<iOSWindowProtocol>

iOSWindow / tvOSWindow / AndroidWindow

iOSAlertWindow

Конкретная технология

моста

Window

AlertWindow

OverlayWindow

View

Page 14: Structural Design Patterns

• Публичный интерфейс (мост) должен быть максимально узким

Особенности моста :• Интерфейс не должен иметь методов, привязанных к конкретным

технологиям или иным зависимостям

• Мы сообщаем об использовании ViewController-а Presenter-уПримеры плохих интерфейсов :

• Мы конфигурируем какую-то кастомную вьюшку напрямую из Presenter-а• Мы высовываем ReactiveCocoa в открытый интерфейс

• Только одна из реализаций ViewController-а для Login-экрана содержит метод для отображения алерта после неудачного логина

MARK: С помощью протоколов это можно обойти : такие методы можно объявлять, как optional (необязательная имплементация)

• Мост - это компиляция концепций абстракции, параллельных иерархий и адаптера

• Это паттерн, в котором очень легко запутаться, и который в разных местах по-разному трактуется. Очень часто путается со «Стратегией». Паттерн в трактовании вызвавший наибольшую головную боль!

Page 15: Structural Design Patterns

• Еще одна задача Моста - обеспечить связь “1 ко многим” (1 ко многим реализациям, или 1 ко многим семействам объектов)

• Можно делать абстракцию и реализации даже в разных проектах. Они настолько отвязаны, что :

• Абстракция самодостаточна сама по себе• Для конкретной реализации нужно только наличие

набора интерфейсов, или базовой соединительной иерархии (подключаем проект или ядро с ней). В некоторых случаях даже они не нужны

Еще некоторые преимущества

Page 16: Structural Design Patterns

• 1) Классический подход с помощью композиции (с использованием адаптера)

• 2) Подход с помощью абстрактного класса

• 3) Подход с помощью протоколов• 4) Подход с помощью isa-swizzling

Способы реализации моста :

Page 17: Structural Design Patterns

Классическая реализация моста

• Применение паттерна “Адаптер” к семейству объектов. • Используется композиция (публичный

объект содержит приватный объект)• Позволяет динамическую подмену

реализации

Page 18: Structural Design Patterns

• Наименее гибкий• Трудно создавать полностью независимые

абстракции• Реализация должна полностью повторять

и реализовывать интерфейс базового класса (интерфейсы идентичны)

Подход с помощью абстрактных классов :

Page 19: Structural Design Patterns

• Не задает жесткой абстракции (имеет опциональные методы)

• Можно дополнить новый любой другой класс протоколом, как новым признаком и поместить в семейство реализаций

• Только для простого моста

Подход с помощью протоколов :

Page 20: Structural Design Patterns

• Использование возможностей Obj-c Runtime. Подмена isa-инварианта класса

• Позволяет динамическую подмену реализации во время выполнения

• Интерфейсы обоих классов могут быть не идентичны, но оба класса должны реализовывать публичный интерфейс. С использованием рантайма можно обойти и этот изъян

• Оба класса обязаны иметь одинаковый Size (кол-во и типы ivar-ов должны быть одинаковы)

Подход с помощью isa-Swizzling :

Page 21: Structural Design Patterns

• Увы, я действительно убедился, что для создания Моста между абстракциями нужен либо крышесносный изврат, либо классическая реализация.

• Только через адаптер можно избежать независимости абстракций

Page 22: Structural Design Patterns

Параллельные иерархии

View

Label

ImageView

VideoView

iOSView

iOSLabel

iOSImageView

iOSVideoView

Page 23: Structural Design Patterns

Параллельные композицииPresenter

View

Interactor

Router

Assembly

ViewInput/ViewOutput

InteractorInput/InteractorOutput

ModuleInput/ModuleOutput

RouterInput

AssemblyProtocol

Presenter

View

Interactor

Router

Assembly

ViewInput/ViewOutput

InteractorInput/InteractorOutput

ModuleInput/ModuleOutput

RouterInput

AssemblyProtocol

Presenter

View

Interactor

Router

Assembly

ViewInput/ViewOutput

InteractorInput/InteractorOutput

ModuleInput/ModuleOutput

RouterInput

AssemblyProtocol

BaseCommon module

Login module

StartGuest module

Page 24: Structural Design Patterns

• Параллельные иерархии - это что-то вроде “двумерного” наследования. Часто используется создание параллельной иерархии через наследование. В этом случае получаем все преимущества наследования.

• Возможно наследование только от одного базового класса, и использование Моста между иерархиями

• Без иного соотнесения - бессмысленно.

• Параллельные композиции имеют смысл - только при полном наследовании целой композиции

• В таком случае мы получаем переиспользуемое взаимодействие между объектами целой подсистемы

Использование параллельности

MARK: Для крупных параллельных подсистем - выгодно использовать кодогенераторы. Generamba for Example

MARK: Для создания юнитов(модулей) параллельных подсистем хорошо используется Абстрактная фабрика

Page 25: Structural Design Patterns

• Параллельные подсистемы могут быть полезны при должном использовании

• Нужно понимать “Нафига”• При добавлении нового уровня параллельности

количество классов и интерфейсов плодится экспоненционально

• Количество классов и без того растет очень шустро, что усложняет работу

• Появляется множество пустых, “зарезервированных” классов

Предостережение

Page 26: Structural Design Patterns

Компоновщик (Composite)

Page 27: Structural Design Patterns

• Мы хотели бы, чтобы один объект мог содержать себе подобные объекты

Мотивация :

• Мы хотели бы детализировать что-либо, атомизировать, и сгруппировать в иерархическую древовидную структуру

• Мы хотели бы иметь способ для представления и сборки составных объектов

• Нужно, чтобы способ взаимодействия с простым объектом, и сколь угодно сложным составным объектом был одинаков

Page 28: Structural Design Patterns

• Иерархичность и совокупность - одни из базовых концепций мироздания, без которых был бы невозможен переход от любых простых форм к более сложным

Отступление про иерархии :

• Компоновщик является одним из центральных паттернов, он помогает строить иерархические структуры. Компоновка основана на базовой концепции Целое-частное.• В реальной жизни объекты компонуются по особенностям строения и назначения, а при разработке - по зонам ответственности

• Без компоновки мы бы работали с ужасными монстрообразными объектами, и тонули бы в сложности и ошибках

Page 29: Structural Design Patterns

UML-диаграмма Компоновщика

• Компоновка - способ составления иерархий• Иерархия - отличный способ контроля над сложностью

• Главное - наличие общего способа взаимодействия - общего интерфейса компоновки

Page 30: Structural Design Patterns

• Иерархии UIView, CALayer-ов, UIViewController-ов

Известные примеры компоновки :• Любые иерархии классов/интерфейсов (у нас единый

способ создания и работы с ними)• Текстовые иерархии (буквы -> слова -> предложения -

> абзацы -> страницы -> статья)• Иерархии выражений : ((a+b) * c) + d . Или например,

иерархии инструкций, операций и операндов в исполнительных файлах• Иерархии сотрудников в различных организациях (сотрудник —> отдел -> организация -> холдинг)

Page 31: Structural Design Patterns

• Храним ли ссылку на родителя? Дочерний объект может как знать, так и не знать о своем родителе. Стоит делать ссылку для сохранения обратной связи, и возможности двустороннего обхода иерархии (не только от общего к частному). Упрощает работу с иерархической структурой

Нюансы реализации:

По сути есть только 3 варианта :

• Только родитель знает о том, что эти объекты - его сыновья• Только сыновья знают о том, кто их родитель

• Все знают обо всех• Например, в случае hit-test-а нужно идти от корневой вьюшки,

и искать наиболее подходящую. Потом все родительские вьюшки, если есть super-вызов оповещаются в touch Began/Moved/End методах

Page 32: Structural Design Patterns

• Иногда у компонента может быть более 1го родителя (например, если использовать ссылки (использовать паттерн Приспособленец)). Например, можно взять одну вьюшку и добавить ее в качестве subview в 2 разных вьюшки

Нюансы реализации:

• Иногда операции для примитивов и составных объектов могут конфликтовать. В общем, на практике приходится либо в базовый класс выносить методы для составных объектов, или использовать проверки на класс (isKindOfClass:)

• Мы должны четко выбрать - прозрачность или безопасность (либо у всех объектов общий и небезопасный интерфейс, либо безопасные, но различные интерфейсы)

• Можно выбирать разные способы упорядочивания потомков (важен ли порядок, например Array или Set)

Page 33: Structural Design Patterns

• С протокол-ориентированной разработкой можно уйти от общего предка, и сделать возможность создания даже нескольких различных вариантов примитивов + набор необязательных методов для составных объектов

• Первым признаком использования паттерну Компоновщик - является использование префиксов parent/child, super/sub

• Для обхода иерархий лучше всего использовать рекурсивные алгоритмы вместе с Итераторами

• Для следования по иерархии вверх следует использовать паттерн Chain of Responsibility (Цепочка обязанностей)

Page 34: Structural Design Patterns

Пример реализации:• Реализуем часть каркаса для событийно-ориентированной

архитектуры. А именно - простое событие и составное событие. Представим, что каждое событие может иметь набор связанных событий, и из них будет составляться иерархическая структура

Page 35: Structural Design Patterns

Пример реализации:• Примеры событий :• Interface Drag&Drop event = Drag event + change Location events +

Drop event• Target-action event = Interface event + Transaction(Action) Events +

Callback event

Page 36: Structural Design Patterns

Пример реализации:

Page 37: Structural Design Patterns

Фасад (Facade)

Page 38: Structural Design Patterns

• Мы бы хотели абстрагироваться от особенностей реализации системы, и иметь возможность управлять ей максимально упрощенно

Мотивация :

• Нам нахрен не надо знать о тысяче внутренних объектов, о нюансах использования, мы просто хотим написать 1-2 строчки / сделать простое действие, и чтобы это заработало!• Так же, как мы не хотим знать о внутренних особенностях класса, так же мы не хотим знать и о внутренних особенностях подсистемы

• Нам нужно минимизировать количество сущностей, которые мы держим в нашей голове. Для огромной системы мы хотели бы мыслить полностью в терминологии ее пакетных подсистем.

Page 39: Structural Design Patterns

Результат :• Паттерн Фасад - унифицированный интерфейс, вместо набора

интерфейсов подсистемы, снижает связность системы, централизует взаимодействие (единая точка входа в систему)

Page 40: Structural Design Patterns

• Фасад хорошо объяснять на примерах из реальной жизни :

• Начальнику не надо знать нюансов работы уборщицы его 5го заместителя

• Мы воспринимаем автомобиль, как почти что цельный объект и в повседневной жизни взаимодействуем с ним, как с транспортным средством. Нам не надо знать об особенностях двигателя и тормозной системы, и нас это очень огорчает, когда нам приходится воспринимать автомобиль, как сложный структурный объект• Мы установили фреймворк Fabric через менеджер зависимостей , и подключили CrashlyticsKit в делегате приложения одной строчкой, и скорее всего, все сразу стали счастливы =)

• Скорее всего, те фреймворки и инструменты, работа с которыми проста, и приносит вам наслаждение - они просто используют принцип фасадности

Page 41: Structural Design Patterns

• Фасад - это та же инкапсуляция, применительно к подсистеме

About Facade :

• Часто оставляют обратную совместимость, и нам можно работать, как с фасадом подсистемы, так и с отдельными модулями подсистемы (например, мы просто подключаем специальный набор библиотек, и работаем уже с внутренностями)

• Фасад - это выделенный интерфейс подсистемы• Фасад - это специальный отдельный интерфейс для

пакета модулей• Если еще точнее - Фасад - это отдельный объект,

который реализует наиболее часто используемые взаимодействия с подсистемой. Он - тот, кто знает, когда и к кому обратиться по адресу

• Резюмируя : Фасад призван упростить нам жизнь

Page 42: Structural Design Patterns

• Можно оставить обратную совместимость, и дать будущее пользователю выбор между простотой и общностью

Нюансы использования и реализации :

• Вместо единого фасада - можно сделать фасадный протокол, и сделать сколько угодно реализаций подсистемы, с полностью разным набором пакетных модулей. Например, мы можем работать на верхнем уровне с MVVM и VIPER-модулями через единый интерфейс, и нам даже не нужно знать, какого типа модуль перед нами

• Мы можем изменять подсистему полностью независимо, главное чтобы у нас был функционирующий фасад.

• Нужно выбирать, какие части подсистемы делать публичными, а какие - приватными. Обычно есть публичный хедер, и остальные модули можно подключить вручную

• Фасад - это не обязательно единый объект. Например, 100 объектов подсистемы могут представляться 3-4мя публичными объектами

Page 43: Structural Design Patterns

• По сути фреймворки по типу Magical Record, Rest Kit - тоже являются фасадами над технологиями более низкого уровня.

• ServiceLocator-объект обеспечивает фасадность над сервисами (это не совсем антипаттерн, просто есть паттерн лучше)

• UIKit тоже основан на CoreFoundation и наборе фреймворков по типу Core Graphics, Core Animation. Он тоже обеспечивает фасадность.• Хороший пример использования - ViperMcFlurry фабрики с методами переходов open чего-то там. В них мы используем в лучшем случае Input/Output протоколы модулей, и не думаем, как устроен конкретный Interactor или View

Примеры использования :

Связи с другими паттернами :• Внутри выгодно использовать абстрактную фабрику для

порождения объектов подсистемы. Абстрактная фабрика может быть альтернативой фасаду

• Похож на медиатора(посредника). Только посредник связывает объекты внутри подсистемы, а фасад обеспечивает способ взаимодействия с системами извне

• В Typhoon - это классы TyphoonAssembly и TyphoonDefinition

Page 44: Structural Design Patterns

• Существует способ создания фасада с помощью множественного наследования.

• Он обладает рядом недостатков, но все-же иногда применим• Фактически, создание объекта, физически объединяющего

интерфейсы нескольких классов в одном - это нарушение SOLID. • Но…

Альтернативный фасад

• Если у нас имеется набор совсем мелких классов с 1-2мя методами в интерфейсе, и у нас есть хороший объединяющий (склеивающий) признак. Например, если у меня user registration service, user authorization service и user token service - я могу из них составить фасад UserService, объединив 3 класса в один. Плюс такого подхода - упрощение работы, и уменьшение количества объектов в системе.

• см. IDCommonFacade

• Это сильно упрощенная фасадность. Ведь у нас нет взаимодействия между объектами подсистемы, и мы не создаем упрощенный интерфейс

Page 45: Structural Design Patterns

Заместитель (Proxy)Alternate Names :

(Surrogate / Promise)

Page 46: Structural Design Patterns

• Для некоторого набора задач мы бы хотели иметь объект, который бы скрывал или заменял реальный объект.

Мотивация :

• Пока объект не готов - вернуть временные заменяющие данные (Placeholder, Dummy Object, Fake data)• Объект в другом адресном пространстве. Например, какой-то объект на удаленном сервере (посол)

• Защищающий заместитель, решающий, предоставлять права пользования объектом, или нет

• Виртуальный заместитель (создающий объект по требованию)

• Экранирующий заместитель (защищает объект от опасных взаимодействий, клиентов)

• Необходимо иметь доступ к объекту через другой объект, который может иметь дополнительную функциональность. При этом извне, чтобы клиенты не знали о существовании объекта-заместителяПримеры использования :

• Различные контейнеры • Другие варианты

Page 47: Structural Design Patterns

UML-диаграмма Proxy

Сравнение Proxy со схожими шаблонами :

• Адаптер обеспечивает отличающийся интерфейс к объекту.

• Прокси обеспечивает тот же самый интерфейс.• Декоратор обеспечивает расширенный интерфейс.

Page 48: Structural Design Patterns

Примеры использования в iOS :• Typhoon - TyphoonReferenceDefinition-объекты, которые

обозначают определения, еще не заполненные инъекциями• Foundation - PlaceholderArray

• ViperMcFlurry - RamblerViperOpenModulePromise - переходные объекты-модули, которые еще не заполнены всеми требуемыми значениями

• В ReactiveCocoa есть Proxy-объекты• В Foundation есть возможность загружать Asset-ы

приложения через Bundle по требованию (On Demand)• В Foundation представляется базовый класс NSProxy

для создания прокси-объектов• OCMock-фреймворк для Unit-тестирования широко

использует концепцию проксирования

Page 49: Structural Design Patterns

Технологии для проксирования в obj-c• NSProxy - облегченная версия NSObject-а, с основным

функционалом для форвардинга (перенаправление сообщения объекта, или подмена реализации)

• Можно переопределить вручную, если переопределить все методы, и направить на соответствующий объект, но это небезопасно. Форвардинг помогает автоматизировать этот процесс.

• В качестве Proxy можно без проблем использовать и любой NSObject-объект

• - (void)forwardInvocation:(NSInvocation *)invocation;

Можно изменить вызов, подделать любой параметр, выполнить произвольный код, в т.ч. перенаправить Invocation на другой объект, и даже подменить селектор!Срабатывает в том случае, если метод не был найден ни в классе, ни в одном из суперклассов

• - (id)forwardingTargetForSelector:(SEL)aSelector

Достаточный метод для простого перенаправления на подходящий таргет (реальный объект. ). Срабатывает, если в классе и суперклассах не реализован метод по заданному селектору. Можно указать таргет, у которого поискать селектор

Page 50: Structural Design Patterns

Приспособленец (Flyweight)

Page 51: Structural Design Patterns

• Объекты не всегда должны знать все, что им нужно для работы

Мотивация :• Мы хотим разграничить внутренние знания и умения объекта с

существующим контекстом (средой деятельности объекта)• Мы не хотим делать всемогущие и всезнающие объекты• Мы хотим иметь возможность использовать один объект в

разных контекстах, или даже в нескольких контекстах одновременно

Примеры из реальной жизни :• Человек и среда. Человек сам по себе обладает рядом умений и

знаний, может быть помещен в самую разную среду (контекст), и в среде его поведение будет обусловлено им самим + условиями и факторами среды

• Графические объекты —> Графический редактор• Табличные Item-ы —> Current Context (number page, page

size) • Текстовые объекты в текстовом редакторе (Информацию о конкретных стилях текста может содержать сам контекст)• Табличные ячейки —> видимая область TableView (ячейки ничего не знают о том, видны они или нет)

• Нам нужна оптимизация, и как-то надо избежать “мульена" схожих объектов

Page 52: Structural Design Patterns

UML-диаграмма Flyweight

• Основной смысл приспособленца - именно в разграничении

• Многие путают его с объектным пулом• Можно было бы назвать также этот паттерн

“Контекстом”.

Page 53: Structural Design Patterns

• Вопрос, как именно реализовать разграничение между внешним и внутренним состоянием

Особенности реализации :

• Как связать (ассоциировать) внешнее состояние с внутренним (можно использовать Dictionary и спец. идентификаторы, например)• Как получить приспособленца? Обычно это делают через Пул объектов

MARK: Еще один пример из жизни : Файл и его метаданные

• Обычно приспособленцы - это либо иммутабельные объекты, либо при получении объекта из пула - он копируется (может использоваться Прототип)

MARK: Еще пример : Ячейки CollectionView и их Layout

• Состояние и Стратегию имеет смысл делать через Приспособленца

Родственные паттерны :

• В целом, не существует канонического способа реализации Flyweight

Page 54: Structural Design Patterns

СПАСИБО ЗА ВНИМАНИЕЕсли у вас возникли вопросы, я с удовольствием обсужу их с вами.

Alexander Babenko (HuktoDev)iOS DeveloperImprove Digital