Порождающие паттерны проектирования — КиберПедия 

История создания датчика движения: Первый прибор для обнаружения движения был изобретен немецким физиком Генрихом Герцем...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...

Порождающие паттерны проектирования

2019-08-07 243
Порождающие паттерны проектирования 0.00 из 5.00 0 оценок
Заказать работу

Порождающие паттерны проектирования

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

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

 

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

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

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

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

 

 

Перечень порождающих паттернов

К порождающим паттернам проектирования относятся следующие:

· абстрактная фабрика (abstract factory);

· строитель (builder);

· фабричный метод (factory method);

· прототип (prototype);

· одиночка (singleton)

 

Абстрактная фабрика

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

 

Назначение

Предоставляет интерфейс для создания семейств, взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.

Достоинства

· изолирует конкретные классы;

  • упрощает замену семейств продуктов;
  • гарантирует сочетаемость продуктов.

 

Недостатки

· сложно добавить поддержку нового вида продуктов.

Применение

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

· Входящие в семейство взаимосвязанные объекты должны использоваться вместе и вам необходимо обеспечить выполнение этого ограничения.

· Система должна конфигурироваться одним из семейств составляющих ее объектов.

· Требуется предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию.

 

 

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

 

Пример

#include <iostream>

 

// Абстрактный ProductA

class Car

{ public:

virtual void info() = 0;

virtual ~Car(){}

};

 

// Конкретный ProductA1

class Ford: public Car

{ public:

virtual void info()

{ std::cout << "Ford" << std::endl; }

};

 

// Конкретный ProductA2

class Toyota: public Car

{ public:

virtual void info()

{ std::cout << "Toyota" << std::endl; }

};

 

// Абстрактный ProductВ

class Engine

{ public:

virtual void getPower() = 0;

virtual ~Engine(){}

};

 

// Конкретный 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 ~ CarFactory(){}

protected:

virtual Car*  createCar() = 0;

virtual Engine* createEngine() = 0;

};

 

// Конкретная Factory1

class FordFactory: public CarFactory

{ protected:

// from CarFactory

virtual Car* createCar()

{ return new Ford(); }

 

virtual Engine* createEngine()

{ return new FordEngine(); }

};

 

// Конкретная Factory2

class ToyotaFactory: public CarFactory

{ protected:

// from CarFactory

virtual Car* createCar()

{ return new Toyota(); }

 

virtual Engine* createEngine()

{ return new ToyotaEngine(); }

};

 

int main()

{

CarFactory* curFactory = NULL;

Car* myCar    = NULL;

Engine* myEngine = NULL;

 

ToyotaFactory toyotaFactory;

FordFactory   fordFactory;

 

curFactory = &toyotaFactory;

 

myCar = curFactory->getNewCar();

myCar->info();

myEngine = curFactory->getNewEngine();

myEngine->getPower();

delete myCar;

delete myEngine;

 

 

curFactory = &fordFactory;

myCar = curFactory->getNewCar();

myCar->info();

myEngine = curFactory->getNewEngine();

myEngine->getPower();

delete myCar;

delete myEngine;

return 0;

}

 

 

Строитель

Назначение

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

Недостатки

· позволяет изменять внутреннее представление продукта;

· изолирует код, реализующий конструирование и представление;

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

Применение

· алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой;

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

 

 

Пример

 

#include <iostream>

#include <memory>

#include <string>

 

// Product

class Pizza

{

private:

std::string dough; //

std::string sauce; // соус

std::string topping; //

 

public:

Pizza() { }

~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> pizza;

public:

PizzaBuilder() {}

virtual ~PizzaBuilder() {}

std::auto_ptr<Pizza> GetPizza() { return pizza; }

 

void createNewPizzaProduct() { pizza.reset (new Pizza); }

 

virtual void buildDough()=0;

virtual void buildSauce()=0;

virtual void buildTopping()=0;

};

 

// ConcreteBuilder

class HawaiianPizzaBuilder: public PizzaBuilder

{

public:

HawaiianPizzaBuilder(): PizzaBuilder() {}

~HawaiianPizzaBuilder(){}

 

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() {}

 ~SpicyPizzaBuilder() {}

 

void buildDough() { pizza->SetDough("pan baked"); }

void buildSauce() { pizza->SetSauce("hot"); }

void buildTopping() { pizza->SetTopping("pepperoni and salami"); }

};

 

// Director

class Waiter

{

private:

PizzaBuilder* pizzaBuilder;

public:

Waiter(): pizzaBuilder(NULL) {}

~Waiter() { }

 

void SetPizzaBuilder(PizzaBuilder* b) { pizzaBuilder = b; }

std::auto_ptr<Pizza> GetPizza() { return pizzaBuilder->GetPizza(); }

void ConstructPizza()

{

   pizzaBuilder->createNewPizzaProduct();

   pizzaBuilder->buildDough();

   pizzaBuilder->buildSauce();

   pizzaBuilder->buildTopping();

}

};

 

// Клиент заказывает две пиццы.

int main()

{

Waiter waiter;

 

HawaiianPizzaBuilder hawaiianPizzaBuilder;

waiter.SetPizzaBuilder (&hawaiianPizzaBuilder);

waiter.ConstructPizza();

std::auto_ptr<Pizza> pizza = waiter.GetPizza();

pizza->ShowPizza();

 

SpicyPizzaBuilder spicyPizzaBuilder;

waiter.SetPizzaBuilder(&spicyPizzaBuilder);

waiter.ConstructPizza();

pizza = waiter.GetPizza();

pizza->ShowPizza();

 

return EXIT_SUCCESS;

}

 

 

 

Фабричный метод

Фабричный метод — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс инстанциировать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне. Также известен под названием виртуальный конструктор.

 

Назначение

Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанциировать. Фабричный метод позволяет классу делегировать создание подклассам. Используется, когда:

· классу заранее неизвестно, объекты каких подклассов ему нужно создавать.

· класс спроектирован так, чтобы объекты, которые он создаёт, специфицировались подклассами.

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

 

Структура

 

  • Product - продукт

- определяет интерфейс объектов, создаваемых абстрактным методом;

  • ConcreteProduct - конкретный продукт

- реализует интерфейс Product;

  • Creator - создатель

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

- может вызывать фабричный метод для создания объекта типа Product;

  • ConcreteCreator - конкретный создатель

- переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект класса ConcreteProduct.

Достоинства

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

Недостатки

  • необходимость создавать наследника Creator для каждого нового типа продукта (ConcreteProduct).

Пример

 

#include<iostream>

#include<string>

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<size; i++)

{ Product* product = creators[i]->FactoryMethod();

cout<<product->getName()<<endl;

   delete product;

}

 

for(int i=0; i<size; i++) delete creators[i];

return 0;

}

 

 

Прототип

Назначение

Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа...

Применимость

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

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

 

 

Пример

Одиночка

Цель

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

Достоинства

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

Недостатки

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

Применение

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

Пример

Возможная реализация на C++ (известная как синглтон Мейерса), где одиночка представляет собой статический локальный объект (важно: это решение не потоко-безопасно и приводится только для того, чтобы показать как устроен шаблон, а не для реального использования в крупномасштабных программных проектах. Кроме того, данная реализация не обеспечивает невозможность создать еще один экземпляр класса).

 

 template<typename T> class Singleton

 {

 

public:

static T& Instance()

{

    static T theSingleInstance; // у класса T есть конструктор по

                                // умолчанию

    return theSingleInstance;

 }

 };

 

 class OnlyOne: public Singleton<OnlyOne>

 {

//.. интерфейс класса

 };

Синглтон на диаграмме классов

 

=====================================================

Основные характеристики

Задача

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

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

Адаптер предусматривает создание класса-оболочки[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()");

}

}

 }

Примечания

  1. Близость значений терминов оболочка и обёртка (англ. wrapper — используется как синоним декоратора) иногда приводит к путанице и Адаптер определяют как синоним шаблона Декоратор, в то время как это два разных шаблона и последний решает иную задачу, а именно: подключение дополнительных обязательств к объекту.
  2. Разница состоит в том, что шаблон Фасад предназначен для упрощения интерфейса, тогда как шаблон Адаптер предназначен для приведения различных существующих интерфейсов к единому требуемому виду.
  3. В языке PHP доступ к СУБД реализован в виде набора функций, для каждой СУБД они имеют различные наименования и, иногда, различный набор используемых параметров, что приводит к значительным проблемам при переходе с одной СУБД на другую, если такой переход заранее не обеспечен использованием шаблона Адаптер.

Цель

При частом изменении класса, преимущества объектно-ориентированного подхода становятся очень полезными, позволяя делать изменения в программе, обладая минимальными сведениями о реализации программы. Шаблон 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();

  }

}

 

/// <summary>

/// Abstraction - абстракция

/// </summary>

/// <remarks>

/// <li>

/// <lu>определяем интерфейс абстракции;</lu>

/// <lu>хранит ссылку на объект <see cref="Implementor"/></lu>

/// </li>

/// </remarks>

class Abstraction

{

protected Implementor implementor;

 

// Property

public Implementor Implementor

{

set{ implementor = value; }

}

 

public virtual void Operation()

{

implementor.Operation();

}

}

 

/// <summary>

/// Implementor - реализатор

/// </summary>

/// <remarks>

/// <li>

/// <lu>определяет интерфейс для классов реализации. Он не обязан точно

/// соотведствовать интерфейсу класса <see cref="Abstraction"/>. На самом деле оба

/// интерфейса могут быть совершенно различны. Обычно интерфейс класса

/// <see cref="Implementor"/> представляет только примитивные операции, а класс

/// <see cref="Abstraction"/> определяет операции более высокого уровня,

/// базирующиеся на этих примитивах;</lu>

/// </li>

/// </remarks>

abstract class Implementor

{

public abstract void Operation();

}

 

/// <summary>

/// RefinedAbstraction - уточненная абстракция

/// </summary>

/// <remarks>

/// <li>

/// <lu>расширяет интерфейс, определенный абстракцией <see cref="Abstraction"/></lu>

/// </li>

/// </remarks>

class RefinedAbstraction: Abstraction

{

public override void Operation()

{

implementor.Operation();

}

}

 

/// <summary>

/// ConcreteImplementor - конкретный реализатор

/// </summary>

/// <remarks>

/// <li>

/// <lu>содержит конкретную реализацию интерфейса <see cref="Implementor"/></lu>

/// </li>

/// </remarks>

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);

   };

}

 

Цель

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

Описание

Примеры

Пример на 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<Graphic> mChildGraphics = new ArrayList<Graphic>();

 

//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();

}

}

Основные характеристики

Задача

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

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

Декоратор предусматривает расширение функциональности объекта без определения подклассов.

Участники

Класс 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();

}

}

 

/// <summary>

/// Component - компонент

/// </summary>

/// <remarks>

/// <li>

/// <lu>определяем интерфейс для объектов, на которые могут быть динамически

/// возложены дополнительные обязанности;</lu>

/// </li>

/// </remarks>

abstract class Component

{

public abstract void Operation();

}

 

/// <summary>

/// ConcreteComponent - конкретный компонент

/// </summary>

/// <remarks>

/// <li>

/// <lu>определяет объект, на который возлагается дополнительные обязаности</lu>

/// </li>

/// </remarks>

class ConcreteComponent: Component

{

public override void Operation()

{

Console.WriteLine("ConcreteComponent.Operation()");

}

}

 

/// <summary>

/// Decorator - декоратор

/// </summary>

/// <remarks>

/// <li>

/// <lu>хранит ссылку на объект <see cref="Component"/> и определяет интерфейс,

/// соотведствующий интерфейсу <see cref="Component"/></lu>

/// </li>

/// </remarks>

abstract class Decorator: Component

{

protected Component component;

 

public void SetComponent(Component component)

{

this.component = component;

}

 

public override void Operation()

{

if (component!= null)

{

   component.Operation();

}

}

}

 

/// <summary>

/// ConcreteDecorator - конкретный декоратор

/// </summary>

/// <remarks>

/// <li>

/// <lu>возглагает дополнительные обязанности на компонент.</lu>

/// </li>

/// </remarks>

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.

 

Порождающие паттерны проектирования

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

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

 

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

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

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

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


Поделиться с друзьями:

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

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

Эмиссия газов от очистных сооружений канализации: В последние годы внимание мирового сообщества сосредоточено на экологических проблемах...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.729 с.