Шаблон проектирования Посредник — Mediator

Десятый шаблон в серии Design Patterns.

Посредник — суть в том, чтобы иметь слабую связь между абстрактными объектами и иметь возможность переопределять конкретные реализации, причем иметь явную связь и инкапсулированное взаимодействие.

Все станет ясней, когда мы перейдем от слов к коду.

Во многих источниках приводится пример чата, когда есть несколько участников чата и любой из них может отправить сообщение и естественно это сообщение должны получить все, кроме отправителя. Здесь явная связь между отправителем и получателем и можно вынести этот механизм в абстрактный класс и интерфейс. А как именно получать сообщения, точнее что с ними делать при получении уже решать в конечных реализациях.

Я же решил придумать иной пример. Предположим у нас некая игра, в которой участвуют несколько игроков. У каждого есть определенное количество единиц здоровья и сила, с которой он может ударить других участников. И при каждом его ходе он атакует всех, кроме себя (здесь кстати можно еще разделить участников на группы и не атаковать «своих»).

Как мне кажется для этой ситуации данный шаблон вполне подходит. Давайте рассмотрим главный интерфейс посредника.

Здесь у нас 1 метод отправки (атаки), где мы укажем объем урона и игрока, который его инициирует. Рассмотрим абстрактный класс пользователя.

Класс отправитель или пользователь имеет в себе ссылку на посредника, которую задаем через конструктор. Также имеем конкретный метод отправки (атаки), в котором указываем только размер урона и вызываем аналогичный метод у посредника, указывая текущего пользователя. Но кроме того, что наш игрок отправляет событие (атакует), он также получает урон, но мы объявляем этот метод абстрактным.

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

Рассмотрим класс посредника.

Здесь у нас список игроков, метод добавления игрока в список и самое интересное, имплементация метода отправки(атаки), мы проходим через весь список и если мы имеем дело не с тем игроком, который вызывает этот метод, то получаем урон.

Нам нужна некая информация по игроку, какой у него запас жизни, какой урон он может наносить и имя. Для этого создадим некий класс, который может хранить эту информацию.

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

А теперь рассмотрим конкретного игрока.

При создании передадим игроку всю информацию и при получении урона (receive) будем уменьшать здоровье (decreaseHealth).

Теперь, чтобы это все посмотреть в действии давайте создадим экран со списком участников.

Нам нужен адаптер для списка.

При создании адаптера просто передадим список данных участников. В конструкторе создаем посредника, наполняем игроками. Когда игрок будет атаковать других будем вызывать метод send и передадим объем урона, после чего просто обновим экран с данными.

Элемент с данными игрока выглядит так

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

Главный экран выглядит максимально просто. Вот разметка

И сам код

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

Что интересно, если бы игроки атаковали снизу вверх, все равно в живых бы остался игрок номер 2.

Итак, плюсы шаблона

Слабая связь между классом и посредником, возможность иметь разные реализации как посредника так и главного класса.

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

 

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Шаблонный метод — Template method

Итак, у нас девятый по счету шаблон из серии Design Patterns.

Шаблонный метод — суть в том, чтобы выносить некоторый шаблонный метод в отдельный класс, а конкретные реализации составных методов оставлять наследникам.

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

Перейдем от слов к коду.

Напишем некий абстрактный класс, который в себе инкапсулирует эти одни и те же действия (алгоритм обработки запросов в сеть — собственно шаблон).

Итак, у нас главный метод showData, в нем и происходит вся логика. Если есть соединение то показываем прогресс (для этого передаем интерфейс вспомогательный), после чего определяем получили ли нужные данные или запрос в сеть закончился ошибкой. Если все ок показыаем данные и скрываем прогресс. Если не удалось получить данные, показываем сообщение об ошибке. Если же соединения с интернетом нет, то также отобразим сообщение о том, что нет соединения.

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

Посмотрим на интерфейс помощника

Простенький интерфейс, который мы заимплементим в активити.

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

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

Итак, давайте для покрытия всех возможных случаев создадим 2 наследника от абстрактного класса — класс для избранных треков и например топ 100.

Здесь мы будем имитировать загрузку данных с сервера, поэтому просто захардкодим некоторые данные и будем отображать их с некоей задержкой в 2 секунды.

Рассмотрим второй класс, где просто вернем информацию, что не удалось загрузить.

Здесь все просто, в методе получения данных просто возвращаем false. Будто наш сервер не вернул то, чего мы от него ожидали, а например ошибку.

Теперь, чтобы все это протестировать нам нужен некий экран с 2 кнопками для избранных треков и топ100. Также будем отображать избранные списком.

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

И наконец посмотри на то, как это все работает.

Создаем 2 экземплара наших классов, передаем им интерейс помощника. При нажатии на кнопки просто вызываем у них шаблонный метод. Вся логика и алгоритм остаются инкапсулированными в абстрактном методе.

На выходе имеем такие скриншоты.

Сначала нажимаем на получение избранных треков, видим прогресс. После чего отображаются все треки (при условии наличия интернета).

После чего нажимаем на топ 100 и получаем ошибку.

Теперь выключим интернет и попробуем опять. Получаем ошибку.

Плюсы шаблона — позволяет выносить и инкапсулировать одни и те же повторяющиеся последовательности методов в отдельный класс.

Минусы — зоопарк начинается тогда, когда кодер пытается использовать его для отличающихся алгоритмов и пихает всякие разные проверки, от чего алгоритм превращается в нечитаемый грязный код.

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Итератор — Iterator

Рассмотрим восьмой шаблон в серии Design Patterns.

Суть шаблона Итератор — предоставление способа последовательного доступа к элементам множества, независимо от его внутреннего состояния.

Этот шаблон наверно самый популярный, так как каждый программист неявно использует его, когда нужно пройтись по всем элементам коллекции.

Перейдем от слов к коду.

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

Сначала обозначим все возможные типы продуктов

У нас будет 4 фильтра — ноутбук, смартфон, телевизор и все продукты.

Далее создадим моделю данных для продукта с 2 полями — тип и имя.

И для простоты создадим некий класс синглтон с некоторыми данными.

10 элементов: 3 ноутбука, 5 смартфонов и 2 телевизора.

Теперь перейдем к самому шаблону. Нам нужен интерфейс итератора.

Итератор должен делать 2 вещи — проверять что есть еще элемент и получать этот следующий элемент.

Напишем некий класс, в котором будет имплентация нашего итератора и инкапсулируем фильтрацию в нем.

Итак, на вход наш класс принимает данные о продуктах и у него 1 метод, получения списка наименований исходя из типа, в нем мы проходим итератором по коллекции и получаем все нужные значения.

Рассмотрим детальней имплементацию итератора.

На вход получаем тип, когда перебираем коллекцию то смотрим, чтобы этот тип совпадал с очередным элементом коллекции или же если тип ВСЕ то пусть вернет нам true, если же совпадения не было, то увеличиваем индекс и идем дальше.

Когда получаем следующий элемент также увеличиваем позицию. Вот и все.

Чтобы было наглядно, рассмотрим разметку главного экрана.

Здесь у нас 4 кнопки для фильтров и сам список продуктов.

И наконец сам код главного экрана.

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

В итоге имеем такие скриншоты.

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

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Стратегия — Strategy

Рассмотрим седьмой шаблон из серии Design Patterns — стратегия.

Суть — инкапсуляция группы алгоритмов и возможность взаимозаменять друг друга без влияния на конечного клиента.

А теперь простыми словами — создаем несколько разных алгоритмов (расчет цены например) и определяем какую стратегию используем в отдельном классе, а в конечном классе просто вызываем метод расчета ничего не зная о методах выбора стртатегии.

Перейдем от слов к коду. Напишем интерфейс стратегии расчета стоимости (например бутылки кока-колы неважно).

Метод получает цену на вход и отдает некоторую обработанную (например уменьшенную за счет скидки).

Создадим некую дефолтную стратегию, где ничего не будем делать с ценой.

Никаких изменений, что пришло на вход то и вернем.

Также создадим 2 скидочные стратегии — с большой скидкой (10%) и маленькой (5%)

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

Устанавливаем количество товара и в зависимости от этого расчитываем конечную стоимость, если количество товара более 30, скидка будет большой — выбираем нужную стратегию, если же количество меньше 30 но больше 10, будет маленькая скидка, а для количества менее 10 скидок нет, выбираем обычную стратегию. Вот и все. Посмотрим разметку главного экрана.

Здесь текстовая информация о скидках, поле ввода для количества и текстовка вывода. Рассмотрим конечный класс.

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

Плюсы шаблона — инкапсуляция всех стратегий и возможность менять схему выбора алгоритма без изменений в конечном классе.

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

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Интерпретатор (Interpreter)

Рассмотрим шестой в серии шаблон — интерпретатор.

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

Согласен, не особо понятно из определения, поэтому скажем простыми словами — шаблон, для интерпретирования некоторого формального скажем языка.

Опять же не особо понятно, давайте перейдем от слов к коду и все станет ясней.

Напишем маленькое упрощенное приложение, где пользователь будет вводить нечто типа 5 + 8, а на выходе будем иметь математическую интерпретацию и посчитаем результат (суть в том, что это выражение является одной строкой, мы его будем разделять на числа и операторы и посчитаем математически какого будет значение).

Для начала нам нужен некий интерфейс выражения.

Так как у нас упрощенный вариант, будем возвращать целочисленное значение.

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

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

Далее создадим 2 простых оператора — прибавление и вычитание.

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

Аналогично для вычитания.

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

Теперь напишем маленький утилитный класс для определения операторов.

Первый метод проверяет, что на вход пришло не число, а оператор, а второй метод определяет какой именно это оператор (в дальнейшем если будут еще операторы, то их придется просто добавить в этот класс.

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

И наконец рассмотрим конечный код.

По нажатию на кнопку интерпретации происходит следующее — получаем текст из поля ввода, интерпретируем и отображаем в текстовке.

Посмотрим детальней сам метод интерпретации.

Будем предполагать, что все наши выражения (числа и операторы) разделены пробелом, потому и преобразуем текст ввода в массив строк. Под него создадим такой же массив для выражений. Проходим циклом по всем элементам и если мы наткнулись на число (не оператор), то сохраняем его в виде числового выражения (nwe Number(items[i]). После чего проходим еще раз циклом и находим все операторы, после чего в результат сохраняем то, что было интерпретировано оператором.

Глянем скиншоты и прольем ясности.

Итак, вводим 5 + 3 и жмем на кнопку, в коде будет следующее — сначала находятся числа 5 и 3 и записываются в ячейки массива, после чего повторным циклом находится оператор «+» и высчитывается сумма располагающихся слева и справа от индекса выражений.

По сути своей шаблоном интерпреатотор является некоторый маленький язык, который получает информацию на вход и переводит ее в другой язык, т.е. интерпретирует по-своему. Для того, чтобы написать хороший интерпретатор понадобится довольно много времени. Один из примеров интерпретатора — Siri, которая преобразует звуковые волны в слова.

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

Минусы — сложность написания действительно хорошего интерпретатора.

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Состояние (State)

Пятый в серии шаблон — Состояние.

Суть — изменение поведения объекта в зависимости от состояния, в котором оно находится.

Зачастую этот шаблон объясняют на примере медиапроигрывателя. Он может находиться в нескольких состояних — проигрывание, пауза и т.д. В зависимости от состояния в котором оно находится, доступные кнопки будут разными и само отображение будет иным (например когда музыка играет может быть анимация).

А я приведу несколько иной пример, более упрощенный.

Предположим у нас есть объект собачка, которая может быть в 3 состояниях — спящем, играющем и обедающим. И наша собачка будет переходить из одного состояния в другое при взаимодействии с ней (тап на экран в андроиде). Перейдем от слов к коду.

Для разных состояний нам понадибится интерфейс.

Будем вызывать событие, параметром получать некий контекст собачки. Также нам понадобится метод, который будет возвращать картинку собачки.

Теперь создадим 3 имплементации для спящей, играющей и обедающей собачки.

В методе взаимодействия выставляем следующее состояние — играющее.

После того как собачка поиграла, пусть поест (context.setDogState(new EatDogState());)

Поели — можно и поспать. Т.е. мы создали цикл состояний — сон, игра, еда, сон, игра, еда. Для того, чтобы все это заработало нам нужен сам контекст собачки.

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

Чтобы стало понятней давайте посмотрим на разметку главного экрана.

Здесь только изображение, по айдишнику будем менять картинку. Теперь сам класс экрана.

Итак, мы инициализируем изображение, создаем контекст собачки (там сразу проставляется состояние сна и проставляется значение изображения). После чего устанавливаем начальное изображение и проставляем картинке собачки обработчик нажатия. При каждом касании на собачку она будет менять свое состояние, после этого будем брать ресурс изображения и отображать собачку в новом состоянии.

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

Плюсы шаблона — инкапсулирование данных состояния, переход из одного в другой, вместо огромной портянки if else if else / switch case case case.. которая захламляет конечный класс. Также всегда можно легко и просто менять цепочку состояний.

Минусы — большое количество мелких классов, но в этом и суть ООП.

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Команда (Command)

Четвертый шаблон проектированя — Команда.

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

Перейдем от мутных слов к простому коду. Рассмотрим пример шаблона на приложении андроид для перевода текста (с англ. языка на русский и обратно для начала). Опять же пример очень упрощен чтобы показать суть шаблона.

Все начинается с интерфейса команды

У него одна единственная функция исполнения, параметром прокинем ему источник текста. Далее нам нужны 2 реализации этого интерфейса.

Эта имплементация для случая перевода с англ. языка на русский, здесь класса принимает в конструкторе некий класс, который отвечает за предоставление данных (это может быть как база данных так и некий интерактор, который будет отправлять запросы в сеть). Аналогично этому классу напишем второй, для перевода с русского языка на английский.

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

Рассмотрим быстренько сам класс, который предоставляет данные (очень упрощено).

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

Но это не все. Еще нам нужен некий класс, который будет отвечать за вызов команды.

Здесь все просто, проставляем команду и вызываем ее метод (команды будем менять часто, потому сеттер, а не параметр конструктора).

Чтобы все это протестировать нам нужна разметка главного экрана, выглядит она примерно так — переключатель языка, поле ввода, текстовка перевода и кнопка перевода текста.

И рассмотрим конечный класс экрана.

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

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

Посмотрим скриншоты чтобы внести ясность.

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

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

Минусы — сложность самого шаблона (которая на среднем уровне на самом деле).

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Наблюдатель (Observer)

Рассмотрим третий шаблон проектирования.

Суть шаблона — изменения в одном объекте порождают изменения во многих других.

Здесь есть 2 понятия — Наблюдатель (observer) и наблюдаемый (subject). В этом шаблоне наблюдателей несколько, а наблюдаемый один.

Перейдем от слов к коду.

Рассмотрим данный шаблон на примере типичной задачи в андроид. Имеется 3 поля ввода и одна кнопка, при нажатии на кнопку необходимо проверить, что поля ввода не пусты перед тем как например отправить на сервер данные (пример очень упрощен).

Для этих целей нам понадобятся интерфейсы наблюдателя и наблюдаемого.

У интерфейса наблюдателя всего 2 метода — обновление, когда произошло изменение и установка наблюдаемого.

Теперь рассмотрим интерфейс самого наблюдаемого.

У наблюдаемого 4 метода, регистрировать наблюдателя чтобы следить за изменениями и отписываться от изменений (unregister), подобно подписке например в YouTube и отписке от новых видео. Далее у нас метод, который отвечает за то, что все наблюдатели получат оповещение об изменении (notifyObservers) и один метод (getUpdate) для того, чтобы каждый из наблюдателей получил само обновление (в сигнатуре метода Object для удобного изменения в дальнейшем).

А теперь рассмотрим реализации этих интерфейсов.

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

А теперь посмотрим на имплементацию наблюдаемого. Так как он у нас будет в единственном числе, необходимо все методы синхронизировать (т.е. ставить в очередь на исполнение, чтобы избежать коллизий). Итак, у наблюдаемого 4 поля: список наблюдателей, так как их будет много, сообщение, которое мы передадим из конечного класса, флаг о том, что произошли изменения и сам мютекс для синхронизации. В методе регистрации наблюдателя проверяем на пустоту и если его нет в списке наблюдателей, то добавляем. В методе отписки точно также проверяем на содержание в списке и убираем из него (все конечно же синхронизированно).

В методе обновления всех наблюдателей необходимо проверить на изменения, если их нет, то и делать ничего не нужно. Если были изменения, то нужно скопировать наблюдатели в локальный список и вызвать у всех метод обновления. Метод получения сообщения обычный геттер. И последний метод, который будем вызывать в конечном классе — postMessage, где мы устанавливаем сообщение об ошибке, меняем флаг о том, что произошли изменения и вызываем метод обновления всех наблюдателей.

Рассмотрим разметку конечного класса.

Здесь все просто — 3 поля ввода и кнопка на дне.

Перейдем к самому классу.

В методе создания экрана инициализируем поля ввода, создаем 3 наблюдателя и наблюдаемое и всем наблюдателям устанавливаем одно и то же наблюдаемое. В методе onResume подписываем всех наблюдателей на наблюдаемое, а в методе onPause отписываем. При нажатии на кнопку постим сообщение ошибки, которое будет отображаться если поля ввода пустые,  далее этот метод вызывает методы обновления у всех наблюдателей и они проверяют на пустоту поля ввода.

Цепочка действий такая onButtonClick (postMessage) -> notifyObservers -> update

Посмотрим на скриншотах как это все выглядит.

Три пустых поля и кнопка, при нажатии у всех полей появится сообщение об ошибке.

Теперь давайте заполним фамилию и имя и посмотрим что будет.

При последующей проверке у 2 из 3 наблюдателей проверка на пустоту прошла и сообщение об ошибке убралось, а на третьем осталась.

Повторю, это очень упрощенный пример, хоть и имеет право на жизнь.

В реальности же у вас могут быть разные поля ввода с разными ошибками, также необходимо будет перед отправкой данных на сервер вернуть в методе postMessage некий булев флаг о том, что ошибок нет и можно отправлять введенные данные. Для этого можно поменять сигнатуры методов update и notifyObservers, чтобы они возвращали флаг отсутствия ошибки.

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

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

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Цепочка обязанностей (Chain of responsibility)

Итак, рассмотрим второй шаблон проектирования.

В чем суть — класс получает задачу на исполнение и если может, то выполняет ее сам, если же нет, передает по цепочке следующему классу, если он есть.

Перейдем от слов к коду. Рассмотрим этот шаблон на примере андроид.

Предположим у нас есть некая модель объекта, которая хранит информацию, которую необходимо отобразить польователю. Если в этой модели есть картинка, то ее отобразим кастомным всплывающим окном, если же есть заголовок, то обычным всплывающим окном, если же только сообщение, то простым тостом (Toast), если же ни под какие критерии не попали — залогируем ошибку. Суть в том, что все модели с информацией обрабатывает главный класс, все проверки находятся внутри (в итоге не нужно писать каждый раз огромные условия — switch case, if else if else if… ).

Итак, нам нужна модель класса

Для обработки информации нам нужен интерфейс, выглядеть он будет примерно так

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

Начнем с самой простой реализации, логирующей ошибку

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

Далее по цепочке у нас обычный тост.

Здесь же мы устанавливаем следующее звено (это будет логирующее). В методе отображения проверяем лишь наличие сообщения в модели данных, если его нет, то передаем по цепочке вниз.

Рассмотрим выше стоящий в этой цепочке элемент, всплывающее окно с заголовком и текстом

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

И самое верхнее звено нашей цепочки, которое может отобразить максимальное количество разнообразной информации пользователю

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

Разметка в нашем случае состоит из картинки, заголовка и описания

А теперь рассмотрим как это все работает, для этого на главном экране создадим 4 кнопки и будем обрабатывать их

Рассмотрим детально код

При создании экрана создаем главное звено цепи —
MessageChain mMainMessageChain = new AlertDialogWithPicMessage(this);

После чего создаем второе звено
MessageChain secondMessageChain = new AlertDialogMessage(this);

Третье звено цепочки обязанностей
MessageChain thirdMessageChain = new ToastMessage(this);

И последнее звено для логирования
MessageChain lastMessageChain = new LogMessage();

После чего третьему звену устанавливаем следующее по цепи последнее, второму звену устанавливаем как следующее звено третье, а первому звену устанавливаем следующим второе звено.

В итоге получается такая последовательность

AlertDialogWithPicMessage -> AlertDialogMessage -> ToastMessage -> LogMessage

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

Для большей ясности посмотрим на скриншоты

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

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

Приведу еще одну параллель с реальной жизнью. Представьте команду разработки, состояющую из Лидера разработки, старшего разработчика, младшего разработчика и стажера. Команде разработки поступает задача. Лидер разработки смотрит на эту задачу и решает, достаточно ли сложная чтобы браться за нее самому или же можно отдать ее старшему разработчику. Или он эту задачу решает сам или передает по цепочке вниз. Старший разработчик смотрит на задачу и если она слишком сложная для младшего разработчика, то делает ее сам, иначе передает младшему. Младший разработчик аналогично исследует задачу, если она примитивна настолько, что ее может решить стажер, то отдает задачу ему, иначе решает ее сам. Стажеру же достаются самые примитивные задачи, с которыми он однозначно сможет справиться.

Плюсы этого шаблона в том, что всегда можно переопределить последовательность цепочки, например отобразить сообщение или главным звеном или залогировать, отбросив 2 средних звена. Также у нас отдельные классы, которые отвечают за обработку метода цепочки.

Минусы этого шаблона в том, что чем длиннее цепочка обязанностей, тем больше проверок будет проходить код.

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования Хранитель (Memento)

Я не буду рассказывать что такое шаблоны проектирования (design patterns), что такое GoF и т.д. Сразу перейду к одному из поведенческих шаблонов — Хранитель.

В чем суть шаблона — позволяет сохранить состояние объекта с последующей возможностью восстановления.

Сразу перейдем от слов к делу. Рассмотрим простой код:

Здесь у нас простой класс с 2 полями. Единственное что нам интересно, так это 2 метода — save и restore. Метод сохранения создает и возвращает новый экземпляр класса Memento, а метод restore восстанавливает данные из объекта такого же класса. Грубо говоря мы переложили данные из нашего класса в некий контейнер и вернули из этого контейнера.

Рассмотрим сам контейнер — класс Хранитель:

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

Теперь же давайте посмотрим простой пример как это все работает:

Сначала мы создаем экземпляр нашего класса с начальными данными 0 и «initial», после этого сохраняем начальное состояние в экземпляре хранителя, после чего меняем значения у исходного класса и восстанавливаем из сохраненного объекта, проверяем поля — юнит-тест проходит.

А теперь рассмотрим плюсы и минусы этого шаблона

Плюсы:
— можно сохранять несколько состояний и восстанавливать их по выбору

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

Альтернатива

Итак, если наш класс хранитель полностью копирует структуру класса, то почему бы не исползовать сам класс? Например так

Единственное отличие от первой версии в том, что в методах сохранения и восстановления используется тот же класс, а не другой.

Проверим на точно таком же тесте, заменив класс хранителя на класс источника:

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

Альтернатива 2

Давайте оставим в классе источника только 1 метод копирования из другого экземпляра.

И будем использовать его как для сохранения состояния так и для восстановления.

Класс хранителя будет выглядеть примерно так