Реферат Паттерны проектирования (шаблоны проектирования). Паттерны проектирования (иначе шаблоны проектирования)
Скачать 425 Kb.
|
Waiter() { }HawaiianPizzaBuilder(){}Pizza() { }Engine(){} Паттерны проектирования (иначе шаблоны проектирования). Ранее мы рассматривали вопросы, связанные с программированием с точки зрения программиста, далее коснемся этих вопросов со стороны проектировщика программных систем. Паттерны проектирования предназначены для: эффективного решения характерных задач проектирования; обобщенного описания решения задачи, которое можно использовать в различных ситуациях; указания отношения и взаимодействия между классами и объектами. Алгоритмы не являются паттернами, т.к. решают задачу вычисления, а не программирования. Они описывают решение задачи по шагам, а не общий подход к ее решению. Паттерны проектирования являются инструментами, призванными помочь в решении широкого круга задач стандартными методами. Что положительное несет использование паттернов при проектировании программных систем. Каждый паттерн описывает решение целого класса проблем.. Каждый паттерн имеет известное имя. Имена паттернов позволяют абстрагироваться от конкретного алгоритма, а решать задачу на уровне общего подхода. Это позволяет облегчить взаимодействие программистов работающих даже на разных языках программирования Правильно сформулированный паттерн проектирования позволяет, отыскав удачное решение, пользоваться им снова и снова. Шаблоны проектирования не зависят от языка программирования (объектно-ориентированного), в отличие от идиом. Идиома (программирование) — низкоуровневый шаблон проектирования, характерный для конкретного языка программирования. Программная идиома — выражение, обозначающее элементарную конструкцию, типичную для одного или нескольких языков программирования. Порождающие паттерны проектирования Абстрагируют процесс инстанцирования. Они позволяют сделать систему независимой от способа создания, композиции и представления объектов. Шаблон, порождающий классы, использует наследование, чтобы изменять инстанцируемый класс, а шаблон, порождающий объекты, делегирует инстанцирование другому объекту. Инстанцирование — создание экземпляра класса. В отличие от слова «создание», применяется не к объекту, а к классу. То есть, говорят: «(в виртуальной среде) создать экземпляр класса или инстанцировать класс». Порождающие шаблоны используют полиморфное инстанцирование. Использование Эти шаблоны оказываются важны, когда система больше зависит от композиции объектов, чем от наследования классов. Основной упор делается не на жестком кодировании фиксированного набора поведений, а на определении небольшого набора фундаментальных поведений, с помощью композиции которых можно получать любое число более сложных. Таким образом, для создания объектов с конкретным поведением требуется нечто большее, чем простое инстанцирование класса. Порождающие шаблоны инкапсулируют знания о конкретных классах, которые применяются в системе. Они скрывают детали того, как эти классы создаются и стыкуются. Единственная информация об объектах, известная системе, — это их интерфейсы, определенные с помощью абстрактных классов. Следовательно, порождающие шаблоны обеспечивают большую гибкость при решении вопроса о том, что создается, кто это создает, как и когда. Иногда допустимо выбирать между тем или иным порождающим шаблоном. Например, есть случаи, когда с пользой для дела можно использовать как прототип, так и абстрактную фабрику. В других ситуациях порождающие шаблоны дополняют друг друга. Так, применяя строитель, можно использовать другие шаблоны для решения вопроса о том, какие компоненты нужно строить, а прототип часто реализуется вместе с одиночкой. Порождающие шаблоны тесно связаны друг с другом, их рассмотрение лучше проводить совместно, чтобы лучше были видны их сходства и различия. Перечень порождающих паттернов К порождающим паттернам проектирования относятся следующие: абстрактная фабрика (abstract factory); строитель (builder); фабричный метод (factory method); прототип (prototype); одиночка (singleton) Абстрактная фабрика Абстрактная фабрика — паттерн позволяющий изменять поведение системы, варьируя создаваемые объекты, при этом сохраняя интерфейсы. Он позволяет создавать целые группы взаимосвязанных объектов (продуктов), которые, будучи созданными одной фабрикой, реализуют общее поведение. Паттерн реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса он может создавать окна, кнопки и т.д.). Затем пишутся наследующиеся от него классы, реализующие этот интерфейс. Иначе говоря, продукт это тот объект, который должен быть произведен, а фабрика предоставляет механизм для его создания. Назначение Предоставляет интерфейс для создания семейств, взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов. Достоинства изолирует конкретные классы; упрощает замену семейств продуктов; гарантирует сочетаемость продуктов. Недостатки сложно добавить поддержку нового вида продуктов. Применение Система не должна зависеть от того, как создаются, компонуются и представляются входящие в нее объекты. Входящие в семейство взаимосвязанные объекты должны использоваться вместе и вам необходимо обеспечить выполнение этого ограничения. Система должна конфигурироваться одним из семейств составляющих ее объектов. Требуется предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию. При данной реализации клиент не должен знать ничего о конкретных классах. Он только запрашивает у фабрики (абстрактной) построение объекта (абстрактного) с соответствующим интерфейсом. Пример #include // Абстрактный ProductA class Car { public: virtual void info() = 0; virtual }; // Конкретный ProductB1 class FordEngine : public Engine { public: virtual void getPower() { std::cout << "Ford Engine 4.4" << std::endl; } }; // Конкретный ProductB2 class ToyotaEngine : public Engine { public: virtual void getPower() { std::cout << "Toyota Engine 3.2" << std::endl; } }; // AbstractFactory class CarFactory { public: Car* getNewCar() { return createCar(); } Engine* getNewEngine() { return createEngine(); } virtual |
позволяет изменять внутреннее представление продукта;
изолирует код, реализующий конструирование и представление;
дает более тонкий контроль над процессом конструирования.
алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой;
процесс конструирования должен обеспечивать различные представления конструируемого объекта.
#include
#include
// Product
class Pizza
{
private:
std::string dough; //
std::string sauce; // соус
std::string topping; //
public:
Pizza() { }
void SetDough(const std::string& d) { dough = d; };
void SetSauce(const std::string& s) { sauce = s; };
void SetTopping(const std::string& t) { topping = t; }
void ShowPizza()
{
std::cout << " Yummy !!!" << std::endl
<< "Pizza with Dough as " << dough
<< ", Sauce as " << sauce
<< " and Topping as " << topping
<< " !!! " << std::endl;
}
};
// Abstract Builder
class PizzaBuilder
{
protected:
std::auto_ptr
pizza;
public:
PizzaBuilder() {}
virtual
void buildDough() { pizza->SetDough("cross"); }
void buildSauce() { pizza->SetSauce("mild"); }
void buildTopping() { pizza->SetTopping("ham and pineapple"); }
};
// ConcreteBuilder
class SpicyPizzaBuilder : public PizzaBuilder
{
public:
SpicyPizzaBuilder() : PizzaBuilder() {}
|
Назначение
Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанциировать. Фабричный метод позволяет классу делегировать создание подклассам. Используется, когда:
классу заранее неизвестно, объекты каких подклассов ему нужно создавать.
класс спроектирован так, чтобы объекты, которые он создаёт, специфицировались подклассами.
класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и планируется локализовать знание о том, какой класс принимает эти обязанности на себя.
Структура
Product - продукт
определяет интерфейс объектов, создаваемых абстрактным методом;
ConcreteProduct - конкретный продукт
реализует интерфейс Product;
Creator - создатель
объявляет фабричный метод, который возвращает объект типа Product. Может также содержать реализацию этого метода "по умолчанию";
может вызывать фабричный метод для создания объекта типа Product;
ConcreteCreator - конкретный создатель
переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект класса ConcreteProduct.
Достоинства
позволяет сделать код создания объектов более универсальным, не привязываясь к конкретным классам (ConcreteProduct), а оперируя лишь общим интерфейсом (Product);
позволяет установить связь между параллельными иерархиями классов.
Недостатки
необходимость создавать наследника Creator для каждого нового типа продукта (ConcreteProduct).
Пример
#include
#include
using namespace std;
// Product
class Product
{ public:
virtual string getName() = 0;
virtual Product(){};
// ConcreteProductA
class ConcreteProductA : public Product
{ public:
string getName()
{ return "ConcreteProductA"; }
};
// ConcreteProductB
class ConcreteProductB : public Product
{ public:
string getName()
{ return "ConcreteProductB"; }
};
// Creator
class Creator
{ public:
virtual Product* FactoryMethod() = 0;
virtual Creator (){};
};
// ConcreteCreatorA
class ConcreteCreatorA : public Creator
{ public:
Product* FactoryMethod()
{ return new ConcreteProductA(); }
};
// ConcreteCreatorB
class ConcreteCreatorB : public Creator
{ public:
Product* FactoryMethod()
{ return new ConcreteProductB(); }
};
int main()
{ const int size = 2;
// Создание массива конкретных creators
Creator* creators[size];
creators[0] = new ConcreteCreatorA();
creators[1] = new ConcreteCreatorB();
// Для каждого из creators создание своего product
for(int i=0; i
{ Product* product = creators[i]->FactoryMethod();
cout<
getName()<
delete product;
}
for(int i=0; i
return 0;
}
Прототип
Назначение
Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа...
Применимость
Используйте этот шаблон проектирования, когда система не должна зависеть от того, как в ней создаются, компонуются и представляются продукты:
инстанцируемые классы определяются во время выполнения, например с помощью динамической загрузки;
для того чтобы избежать построения иерархий классов или фабрик, параллельных иерархии классов продуктов;
экземпляры класса могут находиться в одном из нескольких различных состояний. Может оказаться удобнее установить соответствующее число прототипов и клонировать их, а не инстанцировать каждый раз класс вручную в подходящем состоянии.
Пример
Одиночка
Цель
Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.
Достоинства
контролируемый доступ к единственному экземпляру;
уменьшение числа имён;
допускает уточнение операций и представления;
допускает переменное число экземпляров;
большая гибкость, чем у операций класса.
Недостатки
Глобальные объекты могут быть вредны для объектного программирования, в некоторых случаях приводя к созданию немасштабируемого проекта.
Применение
должен быть ровно один экземпляр некоторого класса, легко доступный всем клиентам;
единственный экземпляр должен расширяться путем порождения подклассов, и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода
Пример
Возможная реализация на C++ (известная как синглтон Мейерса), где одиночка представляет собой статический локальный объект (важно: это решение не потоко-безопасно и приводится только для того, чтобы показать как устроен шаблон, а не для реального использования в крупномасштабных программных проектах. Кроме того, данная реализация не обеспечивает невозможность создать еще один экземпляр класса).
template
{
public:
static T& Instance()
{
static T theSingleInstance; // у класса T есть конструктор по
// умолчанию
return theSingleInstance;
}
};
class OnlyOne : public Singleton
{
//.. интерфейс класса
};
Синглтон на диаграмме классов
=====================================================
Адаптер (шаблон проектирования)
Адаптер, Adapter — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
Основные характеристики
Задача
Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Адаптер применяется если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса.
Способ решения
Адаптер предусматривает создание класса-оболочки[1] с требуемым интерфейсом.
Участники
Класс Adapter приводит интерфейс класса Adaptee в соответствие с интерфейсом класса Target (наследником которого является Adapter). Это позволяет объекту Client использовать объект Adaptee так, словно он является экземпляром класса Target.
Следствия
Шаблон Адаптер позволяет включать уже существующие объекты в новые объектные структуры, независимо от различий в их интерфейсах.
Реализация
Включение уже существующего класса в другой класс. Интерфейс включающего класса приводится в соответствие с новыми требованиями, а вызовы его методов преобразуются в вызовы методов включённого класса.
Замечания и комментарии
Шаблон Адаптер позволяет в процессе проектирования не принимать во внимание возможные различия в интерфейсах уже существующих классов. Если есть класс, обладающий требуемыми методами и свойствами (по крайней мере, концептуально), то при необходимости всегда можно воспользоваться шаблоном Адаптер для приведения его интерфейса к нужному виду.
Близким Адаптеру является шаблон Фасад, не всегда можно отличить один от другого[2].
Применение шаблона
Типичным примером использования шаблона Адаптер можно назвать создание классов, приводящих к единому интерфейсу функции языка PHP обеспечивающие доступ к различным СУБД[3].
Вариант решения данной проблемы с использованием шаблона Адаптер показан на рисунке.
Пример использования шаблона Адаптер
Примеры реализации [показать]
Пример реализации шаблона на C#
using System;
namespace Adapter
{
class MainApp
{
static void Main()
{
// Create adapter and place a request
Target target = new Adapter();
target.Request();
// Wait for user
Console.Read();
}
}
// "Target"
class Target
{
public virtual void Request()
{
Console.WriteLine("Called Target Request()");
}
}
// "Adapter"
class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
// Possibly do some other work
// and then call SpecificRequest
adaptee.SpecificRequest();
}
}
// "Adaptee"
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
}
Примечания
↑ Близость значений терминов оболочка и обёртка (англ. wrapper — используется как синоним декоратора) иногда приводит к путанице и Адаптер определяют как синоним шаблона Декоратор, в то время как это два разных шаблона и последний решает иную задачу, а именно: подключение дополнительных обязательств к объекту.
↑ Разница состоит в том, что шаблон Фасад предназначен для упрощения интерфейса, тогда как шаблон Адаптер предназначен для приведения различных существующих интерфейсов к единому требуемому виду.
↑ В языке PHP доступ к СУБД реализован в виде набора функций, для каждой СУБД они имеют различные наименования и, иногда, различный набор используемых параметров, что приводит к значительным проблемам при переходе с одной СУБД на другую, если такой переход заранее не обеспечен использованием шаблона Адаптер.
Мост (шаблон проектирования)
Bridge, Мост — шаблон проектирования, используемый в проектировании программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон bridge (от англ. — мост) использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.
Цель
При частом изменении класса, преимущества объектно-ориентированного подхода становятся очень полезными, позволяя делать изменения в программе, обладая минимальными сведениями о реализации программы. Шаблон bridge является полезным там, где не только сам класс часто меняется, но и то, что класс делает.
Описание
Когда абстракция и реализация разделены, они могут изменяться независимо. Рассмотрим такую абстракцию как фигура. Существует множество типов фигур, каждая со своими свойствами и методами. Однако есть что-то, что объединяет все фигуры. Например, каждая фигура должна уметь рисовать себя, масштабироваться и т. п. В то же время рисование графики может отличаться в зависимости от типа ОС, или графической библиотеки. Фигуры должны иметь возможность рисовать себя в различных графических средах, но реализовывать в каждой фигуре все способы рисования или модифицировать фигуру каждый раз при изменении способа рисования непрактично. В этом случае помогает шаблон bridge, позволяя создавать новые классы, которые будут реализовывать рисование в различных графических средах. При использовании такого подхода очень легко можно добавлять как новые фигуры, так и способы их рисования.
Примеры
Пример на C#
using System;
namespace Bridge
{
// MainApp test application
class MainApp
{
static void Main()
{
Abstraction ab = new RefinedAbstraction();
// Set implementation and call
ab.Implementor = new ConcreteImplementorA();
ab.Operation();
// Change implemention and call
ab.Implementor = new ConcreteImplementorB();
ab.Operation();
// Wait for user
Console.Read();
}
}
///
/// Abstraction - абстракция
///
///
///
///
///
///
///
class Abstraction
{
protected Implementor implementor;
// Property
public Implementor Implementor
{
set{ implementor = value; }
}
public virtual void Operation()
{
implementor.Operation();
}
}
///
/// Implementor - реализатор
///
///
///
///
/// соотведствовать интерфейсу класса
/// интерфейса могут быть совершенно различны. Обычно интерфейс класса
///
///
/// базирующиеся на этих примитивах;
///
///
abstract class Implementor
{
public abstract void Operation();
}
///
/// RefinedAbstraction - уточненная абстракция
///
///
///
///
///
///
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}
///
/// ConcreteImplementor - конкретный реализатор
///
///
///
///
///
///
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("ConcreteImplementorA Operation");
}
}
// "ConcreteImplementorB"
class ConcreteImplementorB : Implementor
{
public override void Operation()
{
Console.WriteLine("ConcreteImplementorB Operation");
}
}
}
Пример JavaScript
// Implementor ("интерфейс")
function Implementor() {
this.operation = function() {};
}
// ConcreteImplementor (реализация Implementor)
function ConcreteImplementorA() {
this.operation = function() {
alert("ConcreteImplementorA.operation");
};
}
ConcreteImplementorA.prototype = new Implementor();
ConcreteImplementorA.prototype.constructor = ConcreteImplementorA;
function ConcreteImplementorB() {
this.operation = function() {
alert("ConcreteImplementorB.operation");
};
}
ConcreteImplementorB.prototype = new Implementor();
ConcreteImplementorB.prototype.constructor = ConcreteImplementorB;
// Abstraction
function Abstraction() {
var implementor;
this.getImplementor = function() {
// доступ к implementor'у из RefinedAbstraction
return implementor;
};
this.setImplementor = function(val) {
implementor = val;
};
this.operation = function() {
implementor.operation();
};
}
// RefinedAbstraction
function RefinedAbstraction() {
var abstr = new Abstraction();
this.setImplementor = function(val) {
abstr.setImplementor(val);
};
this.operation = function() {
abstr.operation();
};
}
// использование:
var refAbstr = new RefinedAbstraction();
refAbstr.setImplementor( new ConcreteImplementorA() );
refAbstr.operation(); // "ConcreteImplementorA.operation"
refAbstr.setImplementor( new ConcreteImplementorB() );
refAbstr.operation(); // "ConcreteImplementorB.operation"
Без необходимости перегрузки методов Abstraction, можно значительно упростить RefinedAbstraction:
function RefinedAbstraction() {
Abstraction.call(this);
}
Так же можно сохранить ссылки на перегружаемые методы сразу после инстанцирования Abstraction:
function RefinedAbstraction() {
Abstraction.call(this);
var abstr_setImplementor = this.setImplementor;
this.setImplementor = function(val) {
abstr_setImplementor(val);
};
}
Компоновщик (шаблон проектирования)
Компоновщик (англ. Composite pattern) — шаблон проектирования, объединяет объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково.
Цель
Паттерн определяет иерархию классов, которые состоят из примитивных и сложных объектов, упрощает архитектуру клиента, делает процесс добавления новых видов объекта более простым.
Описание
Примеры
Пример на Java
import java.util.List;
import java.util.ArrayList;
/** "Component" */
interface Graphic {
//Prints the graphic.
public void print();
}
/** "Composite" */
class CompositeGraphic implements Graphic {
//Collection of child graphics.
private List
//Prints the graphic.
public void print() {
for (Graphic graphic : mChildGraphics) {
graphic.print();
}
}
//Adds the graphic to the composition.
public void add(Graphic graphic) {
mChildGraphics.add(graphic);
}
//Removes the graphic from the composition.
public void remove(Graphic graphic) {
mChildGraphics.remove(graphic);
}
}
/** "Leaf" */
class Ellipse implements Graphic {
//Prints the graphic.
public void print() {
System.out.println("Ellipse");
}
}
/** Client */
public class Program {
public static void main(String[] args) {
//Initialize four ellipses
Ellipse ellipse1 = new Ellipse();
Ellipse ellipse2 = new Ellipse();
Ellipse ellipse3 = new Ellipse();
Ellipse ellipse4 = new Ellipse();
//Initialize three composite graphics
CompositeGraphic graphic = new CompositeGraphic();
CompositeGraphic graphic1 = new CompositeGraphic();
CompositeGraphic graphic2 = new CompositeGraphic();
//Composes the graphics
graphic1.add(ellipse1);
graphic1.add(ellipse2);
graphic1.add(ellipse3);
graphic2.add(ellipse4);
graphic.add(graphic1);
graphic.add(graphic2);
//Prints the complete graphic (four times the string "Ellipse").
graphic.print();
}
}
Декоратор (шаблон проектирования)
Декоратор, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Известен также под менее распространённым названием Обёртка (Wrapper), которое во многом раскрывает суть реализации шаблона.
Основные характеристики
Задача
Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до, после или даже вместо основной функциональности объекта.
Способ решения
Декоратор предусматривает расширение функциональности объекта без определения подклассов.
Участники
Класс ConcreteComponent — класс, в который с помощью шаблона Декоратор добавляется новая функциональность. В некоторых случаях базовая функциональность предоставляется классами, производными от класса ConcreteComponent. В подобных случаях класс ConcreteComponent является уже не конкретным, а абстрактным. Абстрактный класс Component определяет интерфейс для использования всех этих классов.
Следствия
1. Добавляемая функциональность реализуется в небольших объектах. Преимущество состоит в возможности динамически добавлять эту функциональность до или после основной функциональности объекта ConcreteComponent.
2. Позволяет избегать перегрузки функциональными классами на верхних уровнях иерархии
3. Декоратор и его компоненты не являются идентичными
Реализация
Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.
Замечания и комментарии
Хотя объект-декоратор может добавлять свою функциональность до или после функциональности основного объекта, цепочка создаваемых объектов всегда должна заканчиваться объектом класса ConcreteComponent.
Базовые классы языка Java широко используют шаблон Декоратор для организации обработки операций ввода-вывода.
Применение шаблона
Примеры реализации [показать]
Пример реализации шаблона на C#
using System;
namespace Decorator
{
class MainApp
{
static void Main()
{
// Create ConcreteComponent and two Decorators
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
// Link decorators
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
// Wait for user
Console.Read();
}
}
///
/// Component - компонент
///
///
///
///
/// возложены дополнительные обязанности;
///
///
abstract class Component
{
public abstract void Operation();
}
///
/// ConcreteComponent - конкретный компонент
///
///
///
///
///
///
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("ConcreteComponent.Operation()");
}
}
///
/// Decorator - декоратор
///
///
///
///
/// соотведствующий интерфейсу
///
///
abstract class Decorator : Component
{
protected Component component;
public void SetComponent(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
///
/// ConcreteDecorator - конкретный декоратор
///
///
///
///
///
///
class ConcreteDecoratorA : Decorator
{
private string addedState;
public override void Operation()
{
base.Operation();
addedState = "New State";
Console.WriteLine("ConcreteDecoratorA.Operation()");
}
}
// "ConcreteDecoratorB"
class ConcreteDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();
AddedBehavior();
Console.WriteLine("ConcreteDecoratorB.Operation()");
}
void AddedBehavior()
{
}
}
}
Примеры реализации на JavaScript
Шаблон декоратор в языках с динамической типизацией может быть применен без интерфейсов и традиционного для ООП наследования.
Этот пример скопирован с английской версии статьи. Расчет стоимости кофе:
// ConcreteComponent (класс для последующего декорирования)
function Coffee() {
this.cost = function() {
return 1;
};
}
// Decorator A
function Milk(coffee) {
this.cost = function() {
return coffee.cost() + 0.5;
};
}
// Decorator B
function Whip(coffee) {
this.cost = function() {
return coffee.cost() + 0.7;
};
}
// Decorator C
function Sprinkles(coffee) {
this.cost = function() {
return coffee.cost() + 0.2;
};
}
// Можно использовать, например, так:
var coffee = new Milk(new Whip(new Sprinkles(new Coffee())));
alert( coffee.cost() );
// Или более наглядно:
var coffee = new Coffee();
coffee = new Sprinkles(coffee);
coffee = new Whip(coffee);
coffee = new Milk(coffee);
alert(coffee.cost());
Реализация имеющегося выше C# примера. В ConcreteComponent добавлена локальная переменная price, которая будет изменяться как в нем самом, так и декораторах. Имена классов (кроме постфиксов "A" и "B") совпадают с именами участников шаблона.
function Component() {
this.operation = function() { };
this.getPrice = function() { };
this.setPrice = function() { };
}
function ConcreteComponent() {
var price = 10;
this.operation = function() {
price += 4;
alert("ConcreteComponent.operation, price: "+ price);
};
this.getPrice = function() {
return price;
};
this.setPrice = function(val) {
price = val;
};
}
ConcreteComponent.prototype = new Component();
ConcreteComponent.prototype.constructor = ConcreteComponent;
function Decorator() {
var component;
this.setComponent = function(val) {
component = val;
};
this.getComponent = function() {
return component;
};
this.operation = function() {
component.operation();
};
this.getPrice = function() {
return component.getPrice();
};
this.setPrice = function(val) {
component.setPrice(val);
};
}
Decorator.prototype = new Component();
Decorator.prototype.constructor = Decorator;
function ConcreteDecoratorA() {
Decorator.call(this);
var operation = this.operation; // ссылка на метод, определенный в Decorator
this.operation = function() {
this.setPrice(this.getPrice() + 3);
alert("ConcreteDecoratorA.operation, price: "+ this.getPrice());
operation();
};
}
function ConcreteDecoratorB() {
var dublicate = this; // ссылка на инстанцирующийся объект (т.к. this может меняться)
Decorator.call(this);
var operation = this.operation; // ссылка на метод, определенный в Decorator
this.operation = function() {
this.setPrice(this.getPrice() + 1);
alert("ConcreteDecoratorB.operation, price: "+ this.getPrice());
addedBehavior();
operation();
};
function addedBehavior() {
dublicate.setPrice(dublicate.getPrice() + 2);
alert("addedBehavior, price: "+ dublicate.getPrice());
}
}
// использование
c = new ConcreteComponent();
d1 = new ConcreteDecoratorA();
d2 = new ConcreteDecoratorB();
alert("изначальная цена: " + c.getPrice()); // 10
d1.setComponent(c);
d2.setComponent(d1);
d2.operation();
alert("цена после преобразования: " + c.getPrice()); // 20
Литература
Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб: «Питер», 2007. — С. 366.
Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М.: «Вильямс», 2002. — С. 288.