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

Основные шаблоны

 

1.1 В разработке ПО, шаблон делегирования (англ. delegation pattern) — это способ, которым объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту. Шаблон делегирования является фундаментальной абстракцией, которая поддерживает композицию (также называемую агрегацией), примеси (mixins) и аспекты (aspects).

Пример

Так как C++ не имеет конструкции интерфейса, ту же самую роль играет полностью абстрактный класс.

#include <iostream> class I { public: virtual void f ( void ) = 0; virtual void g ( void ) = 0;}; class A : public I { public: void f ( void ) { std::cout << "A: вызываем метод f()" << std::endl; } void g ( void ) { std::cout << "A: вызываем метод g()" << std::endl; }}; class B : public I { public: void f ( void ) { std::cout << "B: вызываем метод f()" << std::endl; } void g ( void ) { std::cout << "B: вызываем метод g()" << std::endl; }}; class C : public I { public: // Конструктор C() : i ( new A() ) { } // Деструктор virtual ~C() { delete i; } void f ( void ) { i -> f(); } void g ( void ) { i -> g(); } // Этими методами меняем поле-объект, чьи методы будем делегировать void toA ( void ) { delete i; i = new A(); } void toB ( void ) { delete i; i = new B(); } private: // Объявляем объект методы которого будем делегировать I * i;}; int main ( void ) { C * c = new C(); c -> f(); c -> g(); c -> toB(); c -> f(); c -> g(); delete c; return 0;}

Примеры на C#

Пример №1

namespace Patterns{ interface I { void f(); void g(); } class A : I { public void f() { System.Console.WriteLine("A: вызываем метод f()"); } public void g() { System.Console.WriteLine("A: вызываем метод g()"); } } class B : I { public void f() { System.Console.WriteLine("B: вызываем метод f()"); } public void g() { System.Console.WriteLine("B: вызываем метод g()"); } } class C : I { // Создаём объект, методы которого будем делегировать I i = new A(); public void f() { i.f(); } public void g() { i.g(); } // Этими методами меняем поле-объект, чьи методы будем делегировать public void toA() { i = new A(); } public void toB() { i = new B(); } } class DelegatePattern { static void Main( string[] args ) { C c = new C(); c.f(); c.g(); c.toB(); c.f(); c.g(); System.Console.ReadKey(); } }}

Пример 2.

Это пример случая, часто встречающегося в практике. Стоит задача создать класс для хранения списка сотрудников. Данные каждого сотрудника хранятся в объекте класса Employee. Есть уже готовый и стандартный класс для хранения списка объектов Employee. В нём уже реализованы механизмы для работы со списком (к примеру — выделение памяти, добавление и удаление из списка). Наследование класса списка сотрудников от класса списка объектов здесь неприемлемо, потому как мы получим все методы (даже те, которые нас не интересуют). Кроме того нам придётся в некоторых случаях производить приведение типов. Самый элегантный выход из этого случая — делегировать классу списка сотрудников часть методов класса списка объектов. В правилах ООП лучше всего список объектов представить частным (приватным) методом списка сотрудников. В данном случае доступ к списку возможен через свойство.

using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace Employees{ /// <summary> /// Класс для хранения данных о сотруднике. /// </summary> class Employee { private string name; private string department; public Employee(string name, string departament) { this.name = name; this.department = departament; } /// <summary> /// Имя сотрудника. /// </summary> public string Name { get { return this.name; } } /// <summary> /// Отдел работы. /// </summary> public string Department { get { return this.department; } } } /// <summary> /// Класс для хранения списка сотрудников. /// </summary> class EmployeesList { private List<Employee> employees = new List<Employee>(); /// <summary> /// Свойство для получения и записи сотрудника по индексу. /// </summary> /// <param name="index">Индекс сотрудника.</param> /// <returns>Сотрудник.</returns> public Employee this[int index] { get { return employees[index]; } set { employees[index] = value; } } /// <summary> /// Добавление нового сотрудника. /// </summary> /// <param name="employee">Новый сотрудник.</param> public void Add(Employee employee) { employees.Add(employee); } /// <summary> /// Удаление существующего сторудника. /// </summary> /// <param name="employee">Сотрудник для удаления.</param> public void Remove(Employee employee) { employees.Remove(employee); } /// <summary> /// Последовательный поиск сотрудника по имени. /// </summary> /// <param name="name">Имя сотрудника.</param> /// <returns>Индекс сотрудника.</returns> public int GetIndexOfEmployeeByName(string name) { int index = -1; for (int i = 0; i < employees.Count; i++) { if (employees[i].Name == name) { index = i; break; } } return index; } /// <summary> /// Последовательный поиск сотрудника по имени. /// </summary> /// <param name="name">Имя сотрудника.</param> /// <param name="offset">Позиция, с которой следует начинать поиск.</param> /// <returns>Индекс сотрудника.</returns> public int GetIndexOfEmployeeByName(string name, int offset) { int index = -1; for (int i = offset; i < employees.Count; i++) { if (employees[i].Name == name) { index = i; break; } } return index; } } class Program { static void Main(string[] args) { //Создание списка сотрудников и добавление записей в него EmployeesList empList = new EmployeesList(); empList.Add(new Employee("Танасийчук Степан", "web студия")); empList.Add(new Employee("Кусый Назар", "web студия")); empList.Add(new Employee("Сорока Орест", "web студия")); //Поиск сотрудника Кусый Назар и вывод результата при поиске с начала и со 2-ой позиции Console.WriteLine(empList.GetIndexOfEmployeeByName("Кусый Назар").ToString()); Console.WriteLine(empList.GetIndexOfEmployeeByName("Кусый Назар", 2).ToString()); //Поиск и удаление сотрудника Сорока Орест empList.Remove(empList[empList.GetIndexOfEmployeeByName("Сорока Орест")]); } }}

Неизменяемый объект

Объект может быть неизменяемым как полностью, так и частично. Например, применение директивы const к какому-либо члену класса в C++ делает объект частично неизменяемым. В некоторых случаях объект считается неизменяемым с точки зрения пользователя класса, даже если изменяются его внутренние поля. Как правило, неизменяемый объект получает все внутренние значения во время инициализации, либо значения устанавливаются в несколько этапов, но до того, как объект будет использован.

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

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

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

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

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

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

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

 

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

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

Плюсы :

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

 

Пример С++

#include <iostream> // AbstractProductAclass ICar { public: virtual void info() = 0; }; // ConcreteProductA1class Ford : public ICar { public: virtual void info() { std::cout << "Ford" << std::endl; } }; //ConcreteProductA2class Toyota : public ICar { public: virtual void info() { std::cout << "Toyota" << std::endl; } }; // AbstractProductBclass IEngine { public: virtual void getPower() = 0; }; // ConcreteProductB1class FordEngine : public IEngine { public: virtual void getPower() { std::cout << "Ford Engine 4.4" << std::endl; } }; //ConcreteProductB2class ToyotaEngine : public IEngine { public: virtual void getPower() { std::cout << "Toyota Engine 3.2" << std::endl; } }; // AbstractFactoryclass CarFactory { public: ICar* getNewCar() { return createCar(); } IEngine* getNewEngine() { return createEngine(); } protected: virtual ICar* createCar() = 0; virtual IEngine* createEngine() = 0; }; // ConcreteFactory1class FordFactory : public CarFactory { protected: // from CarFactory virtual ICar* createCar() { return new Ford(); } virtual IEngine* createEngine() { return new FordEngine(); } }; // ConcreteFactory2class ToyotaFactory : public CarFactory { protected: // from CarFactory virtual ICar* createCar() { return new Toyota(); } virtual IEngine* createEngine() { return new ToyotaEngine(); } }; int main() { CarFactory* curFactory = NULL; ICar* myCar = NULL; IEngine* 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; }

Примеры на С#

Пример №1

using System; class MainApp { public static void Main() { // Abstract factory #1 AbstractFactory factory1 = new ConcreteFactory1(); Client c1 = new Client(factory1); c1.Run(); // Abstract factory #2 AbstractFactory factory2 = new ConcreteFactory2(); Client c2 = new Client(factory2); c2.Run(); // Wait for user input Console.Read(); } } // "AbstractFactory" abstract class AbstractFactory { public abstract AbstractProductA CreateProductA(); public abstract AbstractProductB CreateProductB(); } // "ConcreteFactory1" class ConcreteFactory1 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA1(); } public override AbstractProductB CreateProductB() { return new ProductB1(); } } // "ConcreteFactory2" class ConcreteFactory2 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA2(); } public override AbstractProductB CreateProductB() { return new ProductB2(); } } // "AbstractProductA" abstract class AbstractProductA { } // "AbstractProductB" abstract class AbstractProductB { public abstract void Interact(AbstractProductA a); } // "ProductA1" class ProductA1 : AbstractProductA { } // "ProductB1" class ProductB1 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine(this.GetType().Name + " interacts with " + a.GetType().Name); } } // "ProductA2" class ProductA2 : AbstractProductA { } // "ProductB2" class ProductB2 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine(this.GetType().Name + " interacts with " + a.GetType().Name); } } // "Client" - the interaction environment of the products class Client { private AbstractProductA AbstractProductA; private AbstractProductB AbstractProductB; // Constructor public Client(AbstractFactory factory) { AbstractProductB = factory.CreateProductB(); AbstractProductA = factory.CreateProductA(); } public void Run() { AbstractProductB.Interact(AbstractProductA); } }

 

 

Пример № 2

namespace AbstractFactory_DesignPattern { using System; // These classes could be part of a framework, // which we will call DP // =========================================== abstract class DPDocument { abstract public void Dump(); } abstract class DPWorkspace { abstract public void Dump(); } abstract class DPView { abstract public void Dump(); } abstract class DPFactory { abstract public DPDocument CreateDocument(); abstract public DPView CreateView(); abstract public DPWorkspace CreateWorkspace(); } abstract class DPApplication { protected DPDocument doc; protected DPWorkspace workspace; protected DPView view; public void ConstructObjects(DPFactory factory) { // Create objects as needed doc = factory.CreateDocument(); workspace = factory.CreateWorkspace(); view = factory.CreateView(); } abstract public void Dump(); public void DumpState() { if (doc != null) doc.Dump(); if (workspace != null) workspace.Dump(); if (view != null) view.Dump(); } } // These classes could be part of an application class MyApplication : DPApplication { MyFactory myFactory = new MyFactory(); override public void Dump() { Console.WriteLine("MyApplication exists"); } public void CreateFamily() { MyFactory myFactory = new MyFactory(); ConstructObjects(myFactory); } } class MyDocument : DPDocument { public MyDocument() { Console.WriteLine("in MyDocument constructor"); } override public void Dump() { Console.WriteLine("MyDocument exists"); } } class MyWorkspace : DPWorkspace { override public void Dump() { Console.WriteLine("MyWorkspace exists"); } } class MyView : DPView { override public void Dump() { Console.WriteLine("MyView exists"); } } class MyFactory : DPFactory { override public DPDocument CreateDocument() { return new MyDocument(); } override public DPWorkspace CreateWorkspace() { return new MyWorkspace(); } override public DPView CreateView() { return new MyView(); } } /// /// Summary description for Client. /// public class Client { public static int Main(string[] args) { MyApplication myApplication = new MyApplication(); myApplication.CreateFamily(); myApplication.DumpState(); return 0; } } }

 

Строитель

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

Достоинтсва :

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

Пример на С++

#include <iostream>#include <memory>#include <string> // Productclass 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 Builderclass 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; }; // ConcreteBuilderclass HawaiianPizzaBuilder : public PizzaBuilder{public: HawaiianPizzaBuilder() : PizzaBuilder() {} ~HawaiianPizzaBuilder(){} void buildDough() { pizza->SetDough("cross"); } void buildSauce() { pizza->SetSauce("mild"); } void buildTopping() { pizza->SetTopping("ham and pineapple"); }}; // ConcreteBuilderclass 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"); }}; // Directorclass 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;}

Пример на С#

namespace Builder_DesignPattern { using System; class Director { public void Construct(AbstractBuilder abstractBuilder) { abstractBuilder.BuildPartA(); abstractBuilder.BuildPartC(); } } abstract class AbstractBuilder { abstract public void BuildPartA(); abstract public void BuildPartB(); abstract public void BuildPartC(); } class ConcreteBuilder : AbstractBuilder { override public void BuildPartA() { Console.WriteLine("ConcreteBuilder.BuildPartA called"); } override public void BuildPartB() { Console.WriteLine("ConcreteBuilder.BuildPartB called"); } override public void BuildPartC() { Console.WriteLine("ConcreteBuilder.BuildPartC called"); } } public class Client { public static int Main(string[] args) { ConcreteBuilder concreteBuilder = new ConcreteBuilder(); Director director = new Director(); director.Construct(concreteBuilder); return 0; } } }

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

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

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

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

Достоинства

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

Пример на С++

# include<iostream># include<string>using namespace std; // "Product"class Product{public: virtual string getName() = 0;}; // "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;}; // "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; // An array of creators Creator* creators[size]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB(); // Iterate over creators and create products for(int i=0;i<size;i++){ Product* product = creators[i]->FactoryMethod(); cout<<product->getName()<<endl; delete product; } int a; cin>>a; for(int i=0;i<size;i++){ delete creators[i]; } return 0;}Пример на С#// Factory Method pattern -- Structural example using System; class MainApp{ static void Main() { // An array of creators Creator[] creators = new Creator[2]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB(); // Iterate over creators and create products foreach(Creator creator in creators) { Product product = creator.FactoryMethod(); Console.WriteLine("Created {0}", product.GetType().Name); } // Wait for user Console.Read(); }} // "Product" abstract class Product{} // "ConcreteProductA" class ConcreteProductA : Product{} // "ConcreteProductB" class ConcreteProductB : Product{} // "Creator" abstract class Creator{ public abstract Product FactoryMethod();} // "ConcreteCreatorA" class ConcreteCreatorA : Creator{ public override Product FactoryMethod() { return new ConcreteProductA(); }} // "ConcreteCreatorB" class ConcreteCreatorB : Creator{ public override Product FactoryMethod() { return new ConcreteProductB(); }}

Прототип

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

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

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

Пример C#

using System; namespace Prototype { class MainApp { static void Main() { // Create two instances and clone each ConcretePrototype1 p1 = new ConcretePrototype1("I"); ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone(); Console.WriteLine ("Cloned: {0}", c1.Id); ConcretePrototype2 p2 = new ConcretePrototype2("II"); ConcretePrototype2 c2 = (ConcretePrototype2)p2.Clone(); Console.WriteLine ("Cloned: {0}", c2.Id); // Wait for user Console.Read(); } } // "Prototype" abstract class Prototype { private string id; // Constructor public Prototype(string id) { this.id = id; } // Property public string Id { get{ return id; } } public abstract Prototype Clone(); } // "ConcretePrototype1" class ConcretePrototype1 : Prototype { // Constructor public ConcretePrototype1(string id) : base(id) { } public override Prototype Clone() { // Shallow copy return (Prototype)this.MemberwiseClone(); } } // "ConcretePrototype2" class ConcretePrototype2 : Prototype { // Constructor public ConcretePrototype2(string id) : base(id) { } public override Prototype Clone() { // Shallow copy return (Prototype)this.MemberwiseClone(); } } }

Одиночка

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

Плюсы

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

Применяется когда

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

Пример C++

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

template<typename T> class Singleton { public: static T& Instance() { static T theSingleInstance; // у класса T есть конструктор по умолчанию return theSingleInstance; } }; class OnlyOne : public Singleton<OnlyOne> { //.. интерфейс класса };

Пример С#

namespace Singleton_DesignPattern { using System; class Singleton { private static Singleton _instance; public static Singleton Instance() { if (_instance == null) _instance = new Singleton(); return _instance; } protected Singleton(){} private int x = 0; public void SetX(int newVal) {x = newVal;} public int GetX(){return x;} } public class Client { public static int Main(string[] args) { int val; Singleton FirstSingleton = Singleton.Instance(); Singleton SecondSingleton = Singleton.Instance(); FirstSingleton.SetX(4); Console.WriteLine("Using first variable for singleton, set x to 4"); val = SecondSingleton.GetX(); Console.WriteLine("Using second variable for singleton, value retrieved = {0}", val); return 0; } } }

Структурные шаблоны

3.1

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

Задача

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

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

Пример С#

namespace Adapter_DesignPattern { using System; class FrameworkXTarget { virtual public void SomeRequest(int x) { } } class FrameworkYAdaptee { public void QuiteADifferentRequest(string str) { Console.WriteLine("FrameworkYAdaptee:QuiteADifferentRequest = {0}", str); } } class OurAdapter : FrameworkXTarget { private FrameworkYAdaptee adaptee = new FrameworkYAdaptee(); override public void SomeRequest(int a) { string b; b = a.ToString(); adaptee.QuiteADifferentRequest(b); } } public class Client { void GenericClientCode(FrameworkXTarget x) { x.SomeRequest(4); } public static int Main(string[] args) { Client c = new Client(); FrameworkXTarget x = new OurAdapter(); c.GenericClientCode(x); return 0; } } }

Мост.

Мост (Bridge) — шаблон проектирования, используемый в проектировании программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон bridge (от англ. — мост) использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами. При частом изменении класса, преимущества объектно-ориентированного подхода становятся очень полезными, позволяя делать изменения в программе, обладая минимальными сведениями о реализации программы. Шаблон bridge является полезным там, где не только сам класс часто меняется, но и то, что класс делает. Когда абстракция и реализация разделены, они могут изменяться независимо. Рассмотрим такую абстракцию как фигура. Существует множество типов фигур, каждая со своими свойствами и методами. Однако есть что-то, что объединяет все фигуры. Например, каждая фигура должна уметь рисовать себя, масштабироваться и т. п. В то же время рисование графики может отличаться в зависимости от типа ОС, или графической библиотеки. Фигуры должны иметь возможность рисовать себя в различных графических средах, но реализовывать в каждой фигуре все способы рисования или модифицировать фигуру каждый раз при изменении способа рисования непрактично. В этом случае помогает шаблон bridge, позволяя создавать новые классы, которые будут реализовывать рисование в различных графических средах. При использовании такого подхода очень легко можно добавлять как новые фигуры, так и способы их рисования.

Пример №1 C#

namespace Bridge_DesignPattern { using System; class Abstraction { protected Implementation impToUse; public void SetImplementation(Implementation i) { impToUse = i; } virtual public void DumpString(string str) { impToUse.DoStringOp(str); } } class DerivedAbstraction_One : Abstraction { override public void DumpString(string str) { str += ".com"; impToUse.DoStringOp(str); } } class Implementation { public virtual void DoStringOp(string str) { Console.WriteLine("Standard implementation - print string as is"); Console.WriteLine("string = {0}", str); } } class DerivedImplementation_One : Implementation { override public void DoStringOp(string str) { Console.WriteLine("DerivedImplementation_One - don't print string"); } } class DerivedImplementation_Two : Implementation { override public void DoStringOp(string str) { Console.WriteLine("DerivedImplementation_Two - print string twice"); Console.WriteLine("string = {0}", str); Console.WriteLine("string = {0}", str); } } public class Client { Abstraction SetupMyParticularAbstraction() { Abstraction a = new DerivedAbstraction_One(); a.SetImplementation(new DerivedImplementation_Two()); return a; } public static int Main(string[] args) { Client c = new Client(); Abstraction a = c.SetupMyParticularAbstraction(); a.DumpString("Clipcode"); return 0; } } }

Пример №2

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

Декоратор

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

Известен также под менее распространённым названием Обёртка (Wrapper), которое во многом раскрывает суть реализации шаблона.

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

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

Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.

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

Пример на C#

namespace Decorator_DesignPattern { using System; abstract class Component { public abstract void Draw(); } class ConcreteComponent : Component { private string strName; public ConcreteComponent(string s) { strName = s; } public override void Draw() { Console.WriteLine("ConcreteComponent - {0}", strName); } } abstract class Decorator : Component { protected Component ActualComponent; public void SetComponent(Component c) { ActualComponent = c; } public override void Draw() { if (ActualComponent != null) ActualComponent.Draw(); } } class ConcreteDecorator : Decorator { private string strDecoratorName; public ConcreteDecorator (string str) { strDecoratorName = str; } public override void Draw() { CustomDecoration(); base.Draw(); } void CustomDecoration() { Console.WriteLine("In ConcreteDecorator: decoration goes here"); Console.WriteLine("{0}", strDecoratorName); } } public class Client { Component Setup() { ConcreteComponent c = new ConcreteComponent("This is the real component"); ConcreteDecorator d = new ConcreteDecorator("This is a decorator for the component"); d.SetComponent(c); return d; } public static int Main(string[] args) { Client client = new Client(); Component c = client.Setup(); c.Draw(); return 0; } } }

3.5 Шаблон Facade (Фасад) — Шаблон проектирования, позволяющий скрыть сложность системы путем сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.

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

Определить одну точку взаимодействия с подсистемой — фасадный объект, обеспечивающий общий интерфейс с подсистемой и возложить на него обязанность по взаимодействию с её компонентами. Фасад — это внешний объект, обеспечивающий единственную точку входа для служб подсистемы. Реализация других компонентов подсистемы закрыта и не видна внешним компонентам. Фасадный объект обеспечивает реализацию паттерна Устойчивый к изменениям (Protected Variations) с точки зрения защиты от изменений в реализации подсистемы.

Шаблон применяется для установки некоторого рода политики по отношению к другой группе объектов. Если политика должна быть яркой и заметной, следует воспользоваться услугами шаблона Фасад. Если же необходимо обеспечить скрытность и аккуратность (прозрачность), более подходящим выбором является шаблон Заместитель (Proxy).

Пример на С#

namespace Facade_DesignPattern { using System; class SubSystem_class1 { public void OperationX() { Console.WriteLine("SubSystem_class1.OperationX called"); } } class SubSystem_class2 { public void OperationY() { Console.WriteLine("SubSystem_class2.OperationY called"); } } class SubSystem_class3 { public void OperationZ() { Console.WriteLine("SubSystem_class3.OperationZ called"); } } class Facade { private SubSystem_class1 c1 = new SubSystem_class1(); private SubSystem_class2 c2 = new SubSystem_class2(); private SubSystem_class3 c3 = new SubSystem_class3(); public void OperationWrapper() { Console.WriteLine("The Facade OperationWrapper carries out complex decision-making"); Console.WriteLine("which in turn results in calls to the subsystem classes"); c1.OperationX(); if (1==1 /* сложное условие */) { c2.OperationY(); } // Большой куслк сложного кода c3.OperationZ(); } } public class Client { public static int Main(string[] args) { Facade facade = new Facade(); Console.WriteLine("Client calls the Facade OperationWrapper"); facade.OperationWrapper(); return 0; } } }

3.6 Шаблон Flyweight (Приспособленец) используется для уменьшения затрат при работе с большим количеством мелких объектов.

#include <map>#include <iostream> // "Flyweight"class Character { public: virtual void display() const = 0; protected: char mSymbol; int mWidth ,mHeight ,mAscent ,mDescent ,mPointSize ; }; // "ConcreteFlyweight"class ConcreteCharacter : public Character { public: // Constructor ConcreteCharacter( const char aSymbol, int aPointSize ) { mSymbol = aSymbol; mHeight = 100; mWidth = 120; mAscent = 70; mDescent = 0; mPointSize = aPointSize; } // from Character virtual void display() const { std::cout << mSymbol << " ( PointSize " << mPointSize << " )\n"; } }; // "FlyweightFactory"template< int POINT_SIZE = 10 >class CharacterFactory { public: const Character& getCharacter( const char aKey ) { // Uses "lazy initialization" Character* character = NULL; Characters::iterator it = mCharacters.find( aKey ); if ( mCharacters.end() == it ) { switch (aKey) { case 'A': character = new ConcreteCharacter('A', POINT_SIZE); break; case 'B': character = new ConcreteCharacter('B', POINT_SIZE); break; //... case 'Z': character = new ConcreteCharacter('Z', POINT_SIZE); break; default: character = new ConcreteCharacter('-', POINT_SIZE); break; } mCharacters.insert( make_pair( aKey, character ) ); } else { character = it->second; } return *character; } private: typedef std::map< char, Character* > Characters; Characters mCharacters; }; int main() { std::string document = "AAZZBBZB"; CharacterFactory<12> characterFactory; std::string::const_iterator it = document.begin(); while( document.end() != it ) { const Character& character = characterFactory.getCharacter( *it++ ); character.display(); } return 0; }

Пример на С#

№1

using System;using System.Collections; namespace Flyweight{ class MainApp { static void Main() { // Build a document with text string document = "AAZZBBZB"; char[] chars = document.ToCharArray(); CharacterFactory f = new CharacterFactory(); // extrinsic state int pointSize = 10; // For each character use a flyweight object foreach (char c in chars) { pointSize++; Character character = f.GetCharacter(c); character.Display(pointSize); } // Wait for user Console.Read(); } } // "FlyweightFactory" class CharacterFactory { private Hashtable characters = new Hashtable(); public Character GetCharacter(char key) { // Uses "lazy initialization" Character character = characters[key] as Character; if (character == null) { switch (key) { case 'A': character = new CharacterA(); break; case 'B': character = new CharacterB(); break; //... case 'Z': character = new CharacterZ(); break; } characters.Add(key, character); } return character; } } // "Flyweight" abstract class Character { protected char symbol; protected int width; protected int height; protected int ascent; protected int descent; protected int pointSize; public abstract void Display(int pointSize); } // "ConcreteFlyweight" class CharacterA : Character { // Constructor public CharacterA() { this.symbol = 'A'; this.height = 100; this.width = 120; this.ascent = 70; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } // "ConcreteFlyweight" class CharacterB : Character { // Constructor public CharacterB() { this.symbol = 'B'; this.height = 100; this.width = 140; this.ascent = 72; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } // ... C, D, E, etc. // "ConcreteFlyweight" class CharacterZ : Character { // Constructor public CharacterZ() { this.symbol = 'Z'; this.height = 100; this.width = 100; this.ascent = 68; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } }}

Пример №2

namespace Flyweight_DesignPattern { using System; using System.Collections; class FlyweightFactory { private ArrayList pool = new ArrayList(); public FlyweightFactory() { /* Элементы в пул могут добавляться либо при запуске, либо по необходимости. */ pool.Add(new ConcreteEvenFlyweight()); pool.Add(new ConcreteUnevenFlyweight()); } public Flyweight GetFlyweight(int key) { int i = key % 2; return((Flyweight)pool[i]); } } abstract class Flyweight { abstract public void DoOperation(int extrinsicState); } class UnsharedConcreteFlyweight : Flyweight { override public void DoOperation(int extrinsicState) { } } class ConcreteEvenFlyweight : Flyweight { override public void DoOperation(int extrinsicState) { Console.WriteLine("In ConcreteEvenFlyweight.DoOperation: {0}", extrinsicState); } } class ConcreteUnevenFlyweight : Flyweight { override public void DoOperation(int extrinsicState) { Console.WriteLine("In ConcreteUnevenFlyweight.DoOperation: {0}", extrinsicState); } } public class Client { public static int Main(string[] args) { int[] data = {1,2,3,4,5,6,7,8}; FlyweightFactory f = new FlyweightFactory(); int extrinsicState = 3; foreach (int i in data) { Flyweight flyweight = f.GetFlyweight(i); flyweight.DoOperation(extrinsicState); } return 0; } } }

3.7 Шаблон Proxy (Заместитель) — Шаблон проектирования. Предоставляет объект, который контролирует доступ к другому объекту через перехват всех вызовов. Необходимо управлять доступом к объекту, так чтобы создавать громоздкие объекты «по требованию».

namespace Proxy_DesignPattern { using System; using System.Threading; abstract class CommonSubject { abstract public void Request(); } class ActualSubject : CommonSubject { public ActualSubject() { Console.WriteLine("Starting to construct ActualSubject"); Thread.Sleep(1000); // Мощные вычисления Console.WriteLine("Finished constructing ActualSubject"); } override public void Request() { Console.WriteLine("Executing request in ActualSubject"); } } class Proxy : CommonSubject { ActualSubject actualSubject; override public void Request() { if (actualSubject == null) actualSubject = new ActualSubject(); actualSubject.Request(); } } public class Client { public static int Main(string[] args) { Proxy p = new Proxy(); if (1==1) // При некотором условии выполняется запрос p.Request(); return 0; } } }

Поведенческие шаблоны

4.1 Команда — шаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры.Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.

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

namespace Command_DesignPattern { using System; abstract class Command { abstract public void Execute(); protected Receiver r; public Receiver R { set { r = value; } } } class ConcreteCommand : Command { override public void Execute() { Console.WriteLine("Command executed"); r.InformAboutCommand(); } } class Receiver { public void InformAboutCommand() { Console.WriteLine("Receiver informed about command"); } } class Invoker { private Command command; public void StoreCommand(Command c) { command = c; } public void ExecuteCommand() { command.Execute(); } } public class Client { public static int Main(string[] args) { Command c = new ConcreteCommand(); Receiver r = new Receiver(); c.R = r; Invoker i = new Invoker(); i.StoreCommand(c); i.ExecuteCommand(); return 0; } } }