Gang of four, the book review. Structural patterns
Gang of four, the book review.
Structural patterns
Структура книги
Книга разделена на две основные части. Превая часть (главы 1 и 2) описывает что есть паттерны проектирования и как они помогают решать вопросы проектирования ПО. Так же в ней включены примеры дизайна для наглядности применения паттернов проектирования на практике.
Вторая часть книги (главы 3, 4, 5) - это непосредственно каталог паттернов. Каталог составляет основную часть книги. Все три главы второй части книги соответствуют трем разым типам паттернов, которые авторы разбили на три группы:
● паттерны создания,● структурные паттерны,● паттерны поведения.
Авторы предлогают использовать книгу несколькими разными способами. От начала и до конца прочитать книгу и по очереди выучить все паттерны. Другой же способ заключается в изучении нужных Вам паттерном и, так как книга "усеяна" ссылками с одного паттерна на другой, то таким образом очень легко улавливать связи между паттернами, как они комбинируются и какие из них подходят друг к другу. Ссылки между книгами можно использовать как логический гайд по книге.
Самой сложной частью дизайна объектно-ориентированных систем - это декомпозиция их на объекты. Эта задача сложна по многим причинам: энкапсуляция, детализация, зависимости, гибкость, производительность, перспективы развития, повторное использование, и т. д. Все это влияет на декомпозицию и зачастую, удовлетворение всех вышеупомянутых причин в дизайне системы, может приводить к конфликтам, например, более гибкая система наверняка будет иметь меньшую производительность.
Подходов к проектированию объектно ориентированных систем - множество. Как вариант, вы можете написать утверждение, которое будет описывать Вашу систему, выделить существительные и глаголы и создать соответствующие классы с методами, на основе выделенных сущностей. Или Вы можете сфокусироваться на сотрудничестве и ответственностях в Вашей системе. Или Вы можете смоделировать "реальный мир" и перенести, найденные в нем в процессе анализа, объекты в дизайн Вашей системы. Всегда будут разногласия, какой подход лучше.
Большинство объектов в дизайне порождается анализом модели. Но часто составление дизайна системы заканчивается на составлении класса, который не имеет предствления в реальном мире. Жесткое моделирование проблем реального мира приводит к тому что система вряд ли сможет решить проблемы "завтрашнего дня". Абстракции, появившиеся в процессе проектирования системы - ключ к гибкой системе.
Паттерны проектирования помогают распознать менее очевидные абстракции и объекты, что, порожденные ими. К примеру, объекты, которые представляют процесс или алгоритм не встречаются в природе, однако они играют важную роль в составлении гибкой системы. Эти объекты редко находятся в процессе анализа или на ранних стадиях проектирования; они появляются позже - в процессе рефакторинга гибкости системы.
Предмет книги
Каждая операция объявленная в объекте определяет имя метода, объекты которые этот метод использует в качестве параметров и результат, возвращаемый этим методом. Это так же известно как сигнатура операции. Набор всех сигнатур операций, определенных в объекте, называют Интерфейсом объекта. Интерфейс объекта характеризует набор запросов, которые можно запрашивать у объекта. Любой запрос, который удовлетворяет какую-либо сигнатуру в интерфейсе объекта, может быть запрошен у объекта.
Тип - это имя, используемое для обозначения определенного интерфейса. Если говорить об объекте типа Окно, значит этот объект должен принимать все запросы, определенные в интерфейсе Окна. Объект может иметь много типов и абсолютно разные объекты могут иметь общие типы. Часть интерфейса объекта может характеризоваться одним типом, а другие части - другими типами. Тип является подтипом другого типа, если его интерфейс содержит подинтерфейс подтипа.
Интерфейсы - основа объектно-ориентированных систем. Объекты общаются только через интерфейсы. Но интерфейсы ничего нам не говорят о реализации.
Динамическое связывание гласит что делая запрос объекту, не требует Вашего представления о реализации, до рантайма. Более того, динамическое связывание позволяет заменять объекты с одинаковыми интерфейсами друг другом в райнтайме. Такие подмены называются полиморфизмом - ключевой концепцией ооп.
Основываясь на вышесказанном, паттерны проектирования помогают Вам определить ключевые елементы в объявлении интерфейсов, а так же исключать не нужные элементы.
Немного теории
Название паттернаНазвание паттерна кратко передает сущность идеи. Правильно имя важно, так как оно становится частью Вашего словаря паттернов.
НамерениеНебольшая секция, отвечающая на вопрос: что делает данный паттерн? Каковы его обоснования и цель? какой конкретный вопрос дизайна он решает?
Так же известен какАльтернативные названия паттерна.
МотивацияСценарий, описывающий проблему дизайна и как объектная структура паттерна решает проблему.
ПрименениеСитуации в которых применение паттерна наиболее обосновано? Примеры плохого дизайна которые решает паттерн. Методы распознавания таких ситуаций.
СтруктураГрафическое представление классов в рассматриваемом паттерне.
УчастникиКлассы и/или объекты, участвующие в дизайне паттерна и их ответственность.
СотрудничествоВзаимодействие участников для достижения цели.
ПоследствияКомпромиссы и результаты использования шаблона.
ПрименениеТонкости и техники о коих нужно знать при использовании шаблона.
Пример кодаПримеры применения паттерна на C++ или Smalltalk.
Известные проблемыПримеры паттерна, найденные в реальных системах.
Связанные паттерныСвязи между паттернами, различия схожих.
Структура описания паттерна
Классификация паттернов
Назначение
Создание Структурирование Поведение
Класс ● Factory Method ● Adapter ● Interpreter
● Template Method
Объект ● Abstract Factory
● Builder
● Prototype
● Singleton
● Adapter
● Bridge
● Composite
● Decorator
● Facade
● Proxy
● Chain of Responsibility
● Command
● Iterator
● Mediator
● Memento
● Flyweight
● Observer
● State
● Strategy
● Visitor
Наб
ор п
атт
ерно
в
ОПРЕДЕЛЕНИЕ
Паттерн проектирования Адаптер упрощает взаимоотношения, путем адаптирования изменений в текущем функционале. Коротко говоря, для его реализации необходимо определить интерфейс, который поможет интегрировать несовместимые компоненты.
Из Wikipedia:"В программировании, паттерн адаптер это один из шаблонов проектирования, который превращает некий интерфейс класса в совместимый интерфейс. Адаптер позволяет классам работать сообща в случаях, когда это невозможно из-за несовместимости интерфейсов, предоставляя клиенту свой интерфейс, в то время как сам по себе он использует оригинальный интерфейс."
Паттерн Adapter
ПРИМЕР ИЗ ЖИЗНИ
Паттерн Adapter
Улетел на малую Родину
Пример кода
Паттерн Adapter
/** * Vendor Interface */interface EmailSubsribe{ public function subscribe(email); public function unsubscribe(email); public function sendUpdates();}
/** * Vendor Interface Implementation */class AdidasEmail implements EmailSubsribe{ public function subscribe(email) { /* code */ } public function unsubscribe(email) { /* code */ } public function sendUpdates() { // Получить доступных подписчиков // Получить доступные обновления // Отослать всем всё }}adidasEmailer = new AdidasEmail();adidasEmailer.sendUpdates();
/** * Vendor new Implementation */class AdidasEmailNew{ public function subscribe(email) { /* code */ } public function unsubscribe(email) { /* code */ } public function getSubscribers() { /* code */ } public function sendEmails(subscribers) { // Получить доступные обновления // Отослать всем всё }}adidasEmailer = new AdidasEmailNew();subscribers = adidasEmailer.getSubscribers();adidasEmailer.sendEmails(subscribers);
Паттерн AdapterСогласно предыдущему коду - послевыхода новой версии класса, измениласьего сигнатура и нарушилась обратная совместимость с предыдущей версиейкласса.
В этой ситуации применить Исходный интерфейс не получится, и именно поэтому нам нужен Адаптер, для того что бы сделать библиотеку совместимой с оригинальным интерфейсом для поддержания согласованности в коде.
/** * Vendor new Implementation */class AdidasEmailAdapter implements EmailSubscribe{ public function subscribe(email) { /* code */ } public function unsubscribe(email) { /* code */ } public function sendUpdates() { adidasEmailer = new AdidasEmailNew(); subscribers = adidasEmailer.getSubscribers(); adidasEmailer.sendEmails(subscribers); }}adidasEmailerAdapter = new AdidasEmailAdapter();adidasEmailerAdapter.sendUpdates();
ОПРЕДЕЛЕНИЕ
Когда абстакция может иметь одну из возможных реализаций, классическим способом согласовать их - это использовать наследование. Абстрактный класс определяет интерфейс абстракции и конкретный класс реализует его своим способом. Но такой не является достаточно гибким. Наследование неизменно связывает реализацию с абстракцией, что делает эту связку трудно поддающуюся модификациям, расширению, повторному использованию абстракции и реализации отдельно друг от друга.
Паттерн проектирования Bridge производит декомпозицию абстракции от ее реализации так, что бы они могли существовать независимо друг от друга.
Паттерн Bridge использует инкапсуляцию, агрегацию и может использовать наследования, для разделения полномочий в разных классах.
Bridge паттерн полезен когда класс и то что он делает зачастую варьируются. Сам по себе класс может быть представлен как реализация, а то что он делает - абстракция. Этот паттерн можно так же представить как двухуровневая абстракция.
Паттерн Bridge
КЛЮЧЕВЫЕ КОМПОНЕНТЫ ПАТТЕРНА BRIDGE
● Абстракция○ определяет интерфейс абстракции○ содержит ссылку на объект типа Исполнитель
● Модернизированная Абстракция○ расширяет интерфейс, определенный абстракцией
● Исполнитель○ определяет интерфейс для классов-сателитов
● Конкретный Исполнитель○ реализация Исполнителя
Паттерн Bridge
Паттерн BridgeКЛАССИЧЕСКИЙ
С ПОМОЩЬЮ BRIDGE
Окно
Linux-Окно Mac-Окно Окно-с-иконкой
Mac-Окно-с-иконкойLinux-Окно-с-иконкой
Окно
Mac-ОкноLinux-Окно
Иконки
Для MacДля Linux
Паттерн Bridge
отдел
сиcтем
Депозитный калькулятор для ПриватБанка
Депозитный калькулятор дляА-Банка
ПРИМЕР ИЗ ЖИЗНИ
Пример кода
Паттерн Bridge
/** "Implementor" */interface DrawingAPI { public void drawCircle(double x, double y, double radius);} /** "ConcreteImplementor" 1/2 */class DrawingAPI1 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.printf("API1.circle at %f:%f radius %f\n", x, y, radius); }} /** "ConcreteImplementor" 2/2 */class DrawingAPI2 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.printf("API2.circle at %f:%f radius %f\n", x, y, radius); }} /** "Abstraction" */abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI){ this.drawingAPI = drawingAPI; } public abstract void draw(); public abstract void resizeByPercentage(double pct);}
/** "Refined Abstraction" */class CircleShape extends Shape { private double x, y, radius; public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) { super(drawingAPI); this.x = x; this.y = y; this.radius = radius; } // low-level i.e. Implementation specific public void draw() { drawingAPI.drawCircle(x, y, radius); } // high-level i.e. Abstraction specific public void resizeByPercentage(double pct) { radius *= pct; }} /** "Client" */class BridgePattern { public static void main(String[] args) { Shape[] shapes = new Shape[] { new CircleShape(1, 2, 3, new DrawingAPI1()), new CircleShape(5, 7, 11, new DrawingAPI2()), }; for (Shape shape : shapes) { shape.resizeByPercentage(2.5); shape.draw(); } }}
ОПРЕДЕЛЕНИЕ
Паттерн Composite собирает объекты в структуру бинарного дерева для представления иерархической структуры. Он так же позволяет клиенту обращаться с отдельными объектами и композициями объектов в одном формате.
В иерархических структурах код для работы с контейнерами и примитивами зачастую отличается, даже если, в основном, обращения одинаковы. Разделив эти объекты, приложение усложняется. Паттерн Composite описывает как использовать рекурсивную композицию, что бы для клиентов не было отличия между контейнерами и примитивами. Пример: графический редактор.
Ключевым игроком Паттерна композит является абстрактный класс, который представляет как контейнер, так и примитив.
Паттерн Composite
КЛЮЧЕВЫЕ КОМПОНЕНТЫ ПАТТЕРНА COMPOSITE
● Компонент○ определяет интерфейс для объектов композиции○ реализует стандартное поведение для интерфейса, общего для всех
классов○ определяет интерфейс для доступа и управления дочерними листками○ [не обязательно] определяет интерфейс для рекурсивного доступа к
родителям в Композиции
● Листок○ представляет объекты Листков в Композиции. Листок не имеет
наследников○ определяет поведение примитивов в Композиции
● Композиция○ определяет поведение для компонентов, имеющих наследников○ имеет наследников○ реализует методы дочерних элементов через интерфейс Компоненты
● Клиент○ управляет объектами Композиции через интерфейс Компоненты
Паттерн Composite
Паттерн Composite
КЛАССИЧЕСКИ
layer = new Layer;layer.elements = [ new Parallelogram(pParams), new Ellipse(eParams), new Triangle(tParams)];layer.drawLayer();each (layer.elements as element) element.drawElement();
слой(композиция)
прим
итив
ы(л
истк
и)
С ПОМОЩЬЮ COMPOSITE
layer = new Layer;layer.elements = [ new Parallelogram(pParams), new Ellipse(eParams), new Triangle(tParams)];layer.draw();
Пример кода
Паттерн Composite
/** "Component" */interface Graphic{ public void print();} /** "Composite" */import java.util.List;import java.util.ArrayList;class CompositeGraphic implements Graphic{ private List<Graphic> childGraphics = new ArrayList<Graphic>();
public void print() { for (Graphic graphic : childGraphics) { graphic.print(); } } public void add(Graphic graphic) { childGraphics.add(graphic); } public void remove(Graphic graphic) { childGraphics.remove(graphic); }}
/** "Leaf" */class Ellipse implements Graphic{ public void print() { System.out.println("Ellipse"); }} /** Client */public class Program{ public static void main(String[] args) { Ellipse ellipse1 = new Ellipse(); Ellipse ellipse2 = new Ellipse(); Ellipse ellipse3 = new Ellipse(); Ellipse ellipse4 = new Ellipse(); CompositeGraphic graphic = new CompositeGraphic(); CompositeGraphic graphic1 = new CompositeGraphic(); CompositeGraphic graphic2 = new CompositeGraphic(); graphic1.add(ellipse1); graphic1.add(ellipse2); graphic1.add(ellipse3); graphic2.add(ellipse4); graphic.add(graphic1); graphic.add(graphic2); graphic.print(); }}
ОПРЕДЕЛЕНИЕ
Добавляет дополнительные возможности объекту динамически. Паттерн декоратор предоставляет гибкую альтернативу наследованию, для более гибкоого расширения функционала.
Паттерн разработан так что бы множественне декораторы могли бы становитсья в стек друг за другом, каждый раз добавляя новую функциональность для перегружаемых (декорируемых) методов.
Паттерн Decorator
КЛЮЧЕВЫЕ КОМПОНЕНТЫ ПАТТЕРНА DECORATOR
● Компонент○ определяет интерфейс для объектов, которые смогут динамически
добавлять функциональность
● Конкретный компонент○ определяет объект, которому можно добавлять функционал
● Декоратор○ определяет интерфейс для объектов которые смогут добавлять себе
функционал динамически○ хранит ссылку на объект-Компонент и определяет интерфейс, который
соответствует интерфейсу Компонента
● Конкретный Декоратор○ добавляет функционао Компоненту
Паттерн Decorator
Паттерн DecoratorНА ПРИМЕРЕ ФОТОШОПЫ
Паттерн DecoratorИЛИ БЕЗ ФОТОШОПЫ
Пример кода
Паттерн Composite
abstract class Window{ public abstract void draw();}
class SimpleWindow extends Window { public void draw() { /*code*/ }}
abstract class WindowDecorator extends Window { protected Window decoratedWindow; public WindowDecorator (Window decoratedWindow) { this.decoratedWindow = decoratedWindow; }
public void draw() { decoratedWindow.draw(); }}
class VerticalScrollBarDecorator extends WindowDecorator { public VerticalScrollBarDecorator (Window decoratedWindow) { super(decoratedWindow); }
@Override public void draw() { super.draw(); drawVerticalScrollBar(); } private void drawVerticalScrollBar() { /*code*/ }}
class HorizontalScrollBarDecorator extends WindowDecorator { public HorizontalScrollBarDecorator (Window decoratedWindow) { super(decoratedWindow); } @Override public void draw() { super.draw(); drawHorizontalScrollBar(); } private void drawHorizontalScrollBar() { /*code*/ }}
public class DecoratedWindowTest{ public static void main(String[] args) { Window decoratedWindow = new HorizontalScrollBarDecorator ( new VerticalScrollBarDecorator(new SimpleWindow()));
}}
ОПРЕДЕЛЕНИЕ
Паттерн проектирования Facade - позволяет скрыть сложность системы путем сведения всех возможных вызовов к одному объекту, делегирующему их соответствующим объектам системы. Реализация компонентов подситемы закрыта и не видна внешним компонентам. Дизайн Facade-объекта защищает его от изменений в реализации подсистемы.
Паттерн Facade
КЛЮЧЕВЫЕ КОМПОНЕНТЫ ПАТТЕРНА FACADE
● Фасад○ знает какие части подсистемы ответственны за выполнения запроса○ делегирует клиентские запросы соответствующим подсистемам
● Подситемы○ выполняет функциональность подситемы○ выполняет работу, делегируеммую объектом Facade○ не имеет представления о Facade, не имеет на него ссылок
Паттерн Facade
ПРИМЕР ИЗ ЖИЗНИ
Паттерн Facade
Хочу стать частным предпринимателем
НалоговаяПенсионный
фонд
Другие службы**
Банк
Единое окно*
?Куда податься ?
* - вопрос почему оно Единое и не единое на самом деле меня тоже гложжет** - санстанция, лицензирование, пожарная безопастность и т. п.
ОПРЕДЕЛЕНИЕ
Паттерн проектирования Proxy - предоставляет объект, который контролирует доступ к другому объекту, перехватывая все его вызовы (выполняет функции контейнера).
Существуют следующие разновидности паттерна:● Протоколирующий прокси● Удаленнй заместитель● Виртуальный заместитель● Копировать-при-записи● Защищающий заместитель● Кеширующий прокси● Экранирующий прокси● Синхронизирующий прокси● Smart reference прокси
Паттерн Proxy
КЛЮЧЕВЫЕ КОМПОНЕНТЫ ПАТТЕРНА FACADE
● Субъект○ интерфейс, определяющий поведение Реального субъекта
● Реальный субъект○ реализует тип субъекта
● Proxy (Заместитель)○ реализует тип субъекта○ дает возможность добавлять поведение в проксируемый объект
Паттерн Proxy
ПРИМЕР ИЗ ЖИЗНИ
FirewallProxy
Паттерн Proxy
Бизнеса банка
Топ менеджеры
Другие клиенты
CacheProxy з е р г и
Пример кода
Паттерн Proxy
interface IMath{ function Add($x, $y); function Sub($x, $y); function Mul($x, $y); function Div($x, $y);} class Math implements IMath{ public function Add($x, $y){return $x + $y;} public function Sub($x, $y){return $x - $y;} public function Mul($x, $y){return $x * $y;} public function Div($x, $y){return $x / $y;}}
class MathProxy implements IMath { protected $math; public function __construct() { $this->math = null; } public function Add($x, $y) { return $x + $y; } public function Sub($x, $y) { return $x - $y; } public function Mul($x, $y) { if ($this->math == null) $this->math = new Math(); return $this->math->Mul($x, $y); } public function Div($x, $y) { if ($this->math == null) $this->math = new Math(); return $this->math->Div($x, $y); }}
$p = new MathProxy; print("4 + 2 = ".$p->Add(4, 2)); print("4 - 2 = ".$p->Sub(4, 2)); print("4 * 2 = ".$p->Mul(4, 2)); print("4 / 2 = ".$p->Div(4, 2));