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