Паттерны проектирования
Порождающие шаблоны проектирования
Эти шаблоны оказываются важны, когда система больше зависит от композиции объектов, чем от наследования классов. Получается так, что основной упор делается не на жестком кодировании фиксированного набора поведений, а на определении небольшого набора фундаментальных поведений, с помощью композиции которых можно получать любое число более сложных. Таким образом, для создания объектов с конкретным поведением требуется нечто большее, чем простое инстанцирование класса.
Порождающие шаблоны инкапсулируют знания о конкретных классах, которые применяются в системе. Они скрывают детали того, как эти классы создаются и стыкуются. Единственная информация об объектах, известная системе, — это их интерфейсы, определенные с помощью абстрактных классов. Следовательно, порождающие шаблоны обеспечивают большую гибкость при решении вопроса о том, что создается, кто это создает, как и когда. Можно собрать систему из «готовых» объектов с самой различной структурой и функциональностью статически (на этапе компиляции) или динамически (во время выполнения).
Абстрактная фабрика (Abstract factory)
Шаблон проектирования - позволяющий изменять поведение системы, варьируя создаваемые объекты, при этом сохраняя интерфейсы. Он позволяет создавать целые группы взаимосвязанных объектов, которые, будучи созданными одной фабрикой, реализуют общее поведение. Шаблон реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса он может создавать окна и кнопки). Затем пишутся наследующиеся от него классы, реализующие этот интерфейс.
Цель
Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.
Плюсы
изолирует конкретные классы;
упрощает замену семейств продуктов;
гарантирует сочетаемость продуктов.
Минусы
сложно добавить поддержку нового вида продуктов.
Применимость
Система не должна зависеть от того, как создаются, компонуются и представляются входящие в нее объекты.
Входящие в семейство взаимосвязанные объекты должны использоваться вместе и вам необходимо обеспечить выполнение этого ограничения.
Система должна конфигурироваться одним из семейств составляющих ее объектов.
Требуется предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию.
Пример применения
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); } }
Строитель (Builder)
Отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.
Плюсы
позволяет изменять внутреннее представление продукта;
изолирует код, реализующий конструирование и представление;
дает более тонкий контроль над процессом конструирования.
Применение
алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой;
процесс конструирования должен обеспечивать различные представления конструируемого объекта.
Объекты «моникер» в COM есть Строитель, инициализируемый строкой. Более того, для его создания используется другой Строитель — MkParseDisplayNameEx, который определяет по строке класс моникера, создает моникер и инициализирует его этой же строкой.
Один из этих объектов, URL Moniker, используется для всей загрузки страниц, вложений и документов в Microsoft Internet Explorer.
Пример использования
using System; using System.Collections.Generic; namespace Builder { public class MainApp { public static void Main() { // Create director and builders Director director = new Director(); Builder b1 = new ConcreteBuilder1(); Builder b2 = new ConcreteBuilder2(); // Construct two products director.Construct(b1); Product p1 = b1.GetResult(); p1.Show(); director.Construct(b2); Product p2 = b2.GetResult(); p2.Show(); // Wait for user Console.Read(); } } // "Director" class Director { // Builder uses a complex series of steps public void Construct(Builder builder) { builder.BuildPartA(); builder.BuildPartB(); } } // "Builder" abstract class Builder { public virtual void BuildPartA(){} public virtual void BuildPartB(){} public virtual Product GetResult(){} } // "ConcreteBuilder1" class ConcreteBuilder1 : Builder { private readonly Product product = new Product(); public override void BuildPartA() { product.Add("PartA"); } public override void BuildPartB() { product.Add("PartB"); } public override Product GetResult() { return product; } } // "ConcreteBuilder2" class ConcreteBuilder2 : Builder { private readonly Product product = new Product(); public override void BuildPartA() { product.Add("PartX"); } public override void BuildPartB() { product.Add("PartY"); } public override Product GetResult() { return product; } } // "Product" class Product { private readonly List parts = new List(); public void Add(string part) { parts.Add(part); } public void Show() { Console.WriteLine("\nProduct Parts -------"); foreach (string part in parts) Console.WriteLine(part); } } }
Фабричный метод (Factory Method)
Шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне. Также известен под названием виртуальный конструктор (Virtual Constructor).
Цель
Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанциировать. Фабричный метод позволяет классу делегировать создание подклассов. Используется, когда:
классу заранее неизвестно, объекты каких подклассов ему нужно создавать.
класс спроектирован так, чтобы объекты, которые он создаёт, специфицировались подклассами.
класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и планируется локализовать знание о том, какой класс принимает эти обязанности на себя.
Плюсы
позволяет сделать код создания объектов более универсальным, не привязываясь к конкретным классам (ConcreteProduct), а оперируя лишь общим интерфейсом (Product);
позволяет установить связь между параллельными иерархиями классов.
Минусы
необходимость создавать наследника Creator для каждого нового типа продукта (ConcreteProduct).
Пример использования
using System; using System.Collections.Generic; namespace Factory { public class MainApp { public static void Main() { // an array of creators Creator[] creators = {new ConcreteCreatorA(), new ConcreteCreatorB()}; // iterate over creators and create products foreach (Creator creator in creators) { Product product = creator.FactoryMethod(); Console.WriteLine("Created {0}", product.GetType()); } // 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)
Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа. Проще говоря, это паттерн создания объекта через клонирование другого объекта вместо создания через конструктор.
Применимость
Паттерн используется чтобы:
избежать дополнительных усилий по созданию объекта стандартным путем (имеется в виду использование ключевого слова 'new', когда вызывается конструктор не только самого объекта, но и конструкторы всей иерархии предков объекта), когда это непозволительно дорого для приложения.
избежать наследования создателя объекта (object creator) в клиентском приложении, как это делает паттерн Abstract Factory.
Используйте этот шаблон проектирования, когда система не должна зависеть от того, как в ней создаются, компонуются и представляются продукты:
инстанцируемые классы определяются во время выполнения, например с помощью динамической загрузки;
для того чтобы избежать построения иерархий классов или фабрик, параллельных иерархии классов продуктов;
экземпляры класса могут находиться в одном из нескольких различных состояний. Может оказаться удобнее установить соответствующее число прототипов и клонировать их, а не инстанцировать каждый раз класс вручную в подходящем состоянии.
Пример использования
using System; namespace Prototype { class MainApp { static void Main() { // Create two instances and clone each Prototype p1 = new ConcretePrototype1("I"); Prototype c1 = p1.Clone(); Console.WriteLine ("Cloned: {0}", c1.Id); Prototype p2 = new ConcretePrototype2("II"); Prototype c2 = 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)
Гарантирует, что у класса есть только один экземпляр, и предоставлятет к нему глобальную точку доступа. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.
Плюсы
контролируемый доступ к единственному экземпляру;
Минусы
глобальные объекты могут быть вредны для объектного программирования, в некоторых случаях приводя к созданию немасштабируемого проекта.
усложняет написание модульных тестов и следование TDD.
Применение
должен быть ровно один экземпляр некоторого класса, легко доступный всем клиентам;
единственный экземпляр должен расширяться путем порождения подклассов, и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода.
Пример использования
public class Singleton { protected Singleton() { } private sealed class SingletonCreator { private static readonly Singleton instance = new Singleton(); public static Singleton Instance { get { return instance; } } } public static Singleton Instance { get { return SingletonCreator.Instance; } } }
Структурные шаблоны проектирования
Структурные шаблоны - шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.
Структурные шаблоны уровня класса используют наследование для составления композиций из интерфейсов и реализаций. Простой пример - использование множественного наследования для объединения нескольких классов в один. В результате получается класс, обладающий свойствами всех своих родителей. Особенно полезен этот шаблон, когда нужно организовать совместную работу нескольких независимо разработанных библиотек.
Адаптер/Обёртка (Adapter/Wrapper)
Структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
Задача
Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Адаптер применяется, если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса.
Решение
Адаптер предусматривает создание класса-оболочки с требуемым интерфейсом.
Участники
Класс Adapter приводит интерфейс класса Adaptee в соответствие с интерфейсом класса Target (наследником которого является Adapter). Это позволяет объекту Client использовать объект Adaptee (посредством адаптера Adapter) так, словно он является экземпляром класса Target. Таким образом Client обращается к интерфейсу Target, реализованному в наследнике Adapter, который перенаправляет обращение к Adaptee.
Пример использования
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()"); } } }
Мост (Bridge)
Шаблон проектирования, используемый в проектировании программного обеспечения чтобы разделять абстракцию и реализацию так, чтобы они могли изменяться независимо. Шаблон Bridge использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.
Цель
При частом изменении класса преимущества объектно-ориентированного подхода становятся очень полезными, позволяя делать изменения в программе, обладая минимальными сведениями о реализации программы. Шаблон Bridge является полезным там, где часто меняется не только сам класс, но и то, что он делает.
Когда абстракция и реализация разделены, они могут изменяться независимо. Другими словами, при реализации через паттерн мост, изменение структуры интерфейса не мешает изменению структуры реализации. Рассмотрим такую абстракцию как фигура. Существует множество типов фигур, каждая со своими свойствами и методами. Однако есть что-то, что объединяет все фигуры. Например, каждая фигура должна уметь рисовать себя, масштабироваться и т. п. В то же время рисование графики может отличаться в зависимости от типа ОС, или графической библиотеки. Фигуры должны иметь возможность рисовать себя в различных графических средах, но реализовывать в каждой фигуре все способы рисования или модифицировать фигуру каждый раз при изменении способа рисования непрактично. В этом случае помогает шаблон Bridge, позволяя создавать новые классы, которые будут реализовывать рисование в различных графических средах. При использовании такого подхода очень легко можно добавлять как новые фигуры, так и способы их рисования. Связь, изображаемая стрелкой на диаграммах, может иметь 2 смысла: а) "разновидность", в соответствии с принципом подстановки Б. Лисков и б) одна из возможных реализаций абстракции. Обычно в языках используется наследование для реализации как а), так и б), что приводит к разбуханию иерархий классов. Мост служит именно для решения этой проблемы: объекты создаются парами из объекта класса иерархии А и иерархии B, наследование внутри иерархии А имеет смысл "разновидность" по Лисков, а для понятия "реализация абстракции" используется ссылка из объекта A в парный ему объект B.
Пример использования
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 implementation and call ab.Implementor = new ConcreteImplementorB(); ab.Operation(); // Wait for user Console.Read(); } } class Abstraction { protected Implementor implementor; // Property public Implementor Implementor { get{ return implementor; } set{ implementor = value; } } public virtual void Operation() { implementor.Operation(); } } abstract class Implementor { public abstract void Operation(); } // "RefinedAbstraction" class RefinedAbstraction : Abstraction { public override void Operation() { implementor.Operation(); } } // "ConcreteImplementorA" class ConcreteImplementorA : Implementor { public override void Operation() { Console.WriteLine("ConcreteImplementorA Operation"); } } // "ConcreteImplementorB" class ConcreteImplementorB : Implementor { public override void Operation() { Console.WriteLine("ConcreteImplementorB Operation"); } } }
Компоновщик (Composite)
Шаблон проектирования, относится к структурным паттернам, объединяет объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково.
Цель
Паттерн определяет иерархию классов, которые одновременно могут состоять из примитивных и сложных объектов, упрощает архитектуру клиента, делает процесс добавления новых видов объекта более простым.
Пример реализации
class MainApp { static void Main() { // Create a tree structure Component root = new Composite("root"); root.Add(new Leaf("Leaf A")); root.Add(new Leaf("Leaf B")); Component comp = new Composite("Composite X"); comp.Add(new Leaf("Leaf XA")); comp.Add(new Leaf("Leaf XB")); root.Add(comp); root.Add(new Leaf("Leaf C")); // Add and remove a leaf Leaf leaf = new Leaf("Leaf D"); root.Add(leaf); root.Remove(leaf); // Recursively display tree root.Display(1); // Wait for user Console.Read(); } } abstract class Component { protected string name; // Constructor public Component(string name) { this.name = name; } public abstract void Add(Component c); public abstract void Remove(Component c); public abstract void Display(int depth); } class Composite : Component { private ArrayList children = new ArrayList(); // Constructor public Composite(string name) : base(name) { } public override void Add(Component component) { children.Add(component); } public override void Remove(Component component) { children.Remove(component); } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + name); // Recursively display child nodes foreach (Component component in children) { component.Display(depth + 2); } } } class Leaf : Component { // Constructor public Leaf(string name) : base(name) { } public override void Add(Component c) { Console.WriteLine("Cannot add to a leaf"); } public override void Remove(Component c) { Console.WriteLine("Cannot remove from a leaf"); } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + name); } }
Декоратор (Decorator)
Шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности. Известен также под менее распространённым названием Обёртка (Wrapper), которое во многом раскрывает суть реализации шаблона.
Задача
Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до, после или даже вместо основной функциональности объекта.
Решение
Декоратор предусматривает расширение функциональности объекта без определения подклассов.
Участники
Класс ConcreteComponent — класс, в который с помощью шаблона Декоратор добавляется новая функциональность. В некоторых случаях базовая функциональность предоставляется классами, производными от класса ConcreteComponent. В подобных случаях класс ConcreteComponent является уже не конкретным, а абстрактным. Абстрактный класс Component определяет интерфейс для использования всех этих классов.
Следствия
Добавляемая функциональность реализуется в небольших объектах. Преимущество состоит в возможности динамически добавлять эту функциональность до или после основной функциональности объекта ConcreteComponent.
Позволяет избегать перегрузки функциональными классами на верхних уровнях иерархии
Декоратор и его компоненты не являются идентичными
Реализация
Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта. При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.
Комментарии
Хотя объект-декоратор может добавлять свою функциональность до или после функциональности основного объекта, цепочка создаваемых объектов всегда должна заканчиваться объектом класса ConcreteComponent.
Базовые классы языка Java широко используют шаблон Декоратор для организации обработки операций ввода-вывода.
И декоратор, и адаптер являются обертками вокруг объекта — хранят в себе ссылку на оборачиваемый объект и часто передают в него вызовы методов. Отличие декоратора от адаптера в том, что адаптер имеет внешний интерфейс, отличный от интерфейса оборачиваемого объекта, и используется именно для стыковки разных интерфейсов. Декоратор же имеет точно такой же интерфейс, и используется для добавления функциональности.
Для расширения функциональности класса возможно использовать как декораторы, так и стратегии. Декораторы оборачивают объект снаружи, стратегии же вставляются в него внутрь по неким интерфейсам. Недостаток стратегии: класс должен быть спроектирован с возможностью вставления стратегий, декоратор же не требует такой поддержки. Недостаток декоратора: он оборачивает ровно тот же интерфейс, что предназначен для внешнего мира, что вызывает смешение публичного интерфейса и интерфейса кастомизации, которое не всегда желательно.
Применение шаблона
Драйверы-фильтры в ядре Windows (архитектура WDM) представляют собой декораторы. Несмотря на то, что WDM реализована на не-объектном языке Си, в ней четко прослеживаются паттерны проектирования — декоратор, цепочка ответственности, и команда (объект IRP).
Архитектура COM не поддерживает наследование реализаций, вместо него предлагается использовать декораторы (в данной архитектуре это называется «агрегация»). При этом архитектура решает (с помощью механизма pUnkOuter) проблему object identity, возникающую при использовании декораторов — identity агрегата есть identity его самого внешнего декоратора.
Пример реализации
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(); } } abstract class Component { public abstract void Operation(); } class ConcreteComponent : Component { public override void Operation() { Console.WriteLine("ConcreteComponent.Operation()"); } } abstract class Decorator : Component { protected Component component; public void SetComponent(Component component) { this.component = component; } public override void Operation() { if (component != null) { component.Operation(); } } } 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() { } } }
Фасад (Facade)
Шаблон проектирования, позволяющий скрыть сложность системы путем сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.
Проблема
Как обеспечить унифицированный интерфейс с набором разрозненных реализаций или интерфейсов, например, с подсистемой, если нежелательно высокое связывание с этой подсистемой или реализация подсистемы может измениться?
Решение
Определить одну точку взаимодействия с подсистемой — фасадный объект, обеспечивающий общий интерфейс с подсистемой и возложить на него обязанность по взаимодействию с её компонентами. Фасад — это внешний объект, обеспечивающий единственную точку входа для служб подсистемы. Реализация других компонентов подсистемы закрыта и не видна внешним компонентам. Фасадный объект обеспечивает реализацию паттерна Устойчивый к изменениям (Protected Variations) с точки зрения защиты от изменений в реализации подсистемы.
Особенности
Шаблон применяется для установки некоторого рода политики по отношению к другой группе объектов. Если политика должна быть яркой и заметной, следует воспользоваться услугами шаблона Фасад. Если же необходимо обеспечить скрытность и аккуратность (прозрачность), более подходящим выбором является шаблон Заместитель (Proxy).
Пример реализации
using System; namespace Library { internal class SubsystemA { internal string A1() { return "Subsystem A, Method A1\n"; } internal string A2() { return "Subsystem A, Method A2\n"; } } internal class SubsystemB { internal string B1() { return "Subsystem B, Method B1\n"; } } internal class SubsystemC { internal string C1() { return "Subsystem C, Method C1\n"; } } } public static class Facade { static Library.SubsystemA a = new Library.SubsystemA(); static Library.SubsystemB b = new Library.SubsystemB(); static Library.SubsystemC c = new Library.SubsystemC(); public static void Operation1() { Console.WriteLine("Operation 1\n" + a.A1() + a.A2() + b.B1()); } public static void Operation2() { Console.WriteLine("Operation 2\n" + b.B1() + c.C1()); } } class Program { static void Main(string[] args) { Facade.Operation1(); Facade.Operation2(); // Wait for user Console.Read(); } }
Заместитель (Proxy, Surrogate, Placeholder)
Шаблон проектирования, который предоставляет объект, который контролирует доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера).
Проблема
Необходимо управлять доступом к объекту так, чтобы создавать громоздкие объекты «по требованию».
Решение
Создать суррогат громоздкого объекта. «Заместитель» хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту (объект класса «Заместитель» может обращаться к объекту класса «Субъект», если интерфейсы «Реального Субъекта» и «Субъекта» одинаковы). Поскольку интерфейс «Реального Субъекта» идентичен интерфейсу «Субъекта», так, что «Заместителя» можно подставить вместо «Реального Субъекта», контролирует доступ к «Реальному Субъекту», может отвечать за создание или удаление «Реального Субъекта». «Субъект» определяет общий для «Реального Субъекта» и «Заместителя» интерфейс, так, что «Заместитель» может быть использован везде, где ожидается «Реальный Субъект». При необходимости запросы могут быть переадресованы «Заместителем» «Реальному Субъекту».
Преимущества и недостатки от применения
Преимущества:
удаленный заместитель;
виртуальный заместитель может выполнять оптимизацию;
защищающий заместитель;
"умная" ссылка;
Недостатки
резкое увеличение времени отклика.
Сфера применения
Шаблон Proxy может применяться в случаях работы с сетевым соединением, с огромным объектом в памяти (или на диске) или с любым другим ресурсом, который сложно или тяжело копировать. Хорошо известный пример применения - объект, подсчитывающий число ссылок.
Пример реализации
using System; using System.Threading; class MainApp { static void Main() { // Create math proxy IMath p = new MathProxy(); // Do the math Console.WriteLine("4 + 2 = " + p.Add(4, 2)); Console.WriteLine("4 - 2 = " + p.Sub(4, 2)); Console.WriteLine("4 * 2 = " + p.Mul(4, 2)); Console.WriteLine("4 / 2 = " + p.Div(4, 2)); // Wait for user Console.Read(); } } public interface IMath { double Add(double x, double y); double Sub(double x, double y); double Mul(double x, double y); double Div(double x, double y); } class Math : IMath { public Math() { Console.WriteLine("Create object Math. Wait..."); Thread.Sleep(1000); } public double Add(double x, double y){return x + y;} public double Sub(double x, double y){return x - y;} public double Mul(double x, double y){return x * y;} public double Div(double x, double y){return x / y;} } class MathProxy : IMath { Math math; public MathProxy() { math = null; } public double Add(double x, double y) { return x + y; } public double Sub(double x, double y) { return x - y; } public double Mul(double x, double y) { if (math == null) math = new Math(); return math.Mul(x, y); } public double Div(double x, double y) { if (math == null) math = new Math(); return math.Div(x, y); } }
Поведенческие шаблоны проектирования
Поведенческие шаблоны (behavioral patterns) — шаблоны проектирования, определяющие алгоритмы и способы реализации взаимодействия различных объектов и классов.
В поведенческих шаблонах уровня класса используется наследование, чтобы определить поведение для различных классов. В поведенческих шаблонах уровня объекта используется композиция. Некоторые из них описывают, как с помощью кооперации несколько равноправных объектов работают над заданием, которое они не могут выполнить по отдельности. Здесь важно то, как объекты получают информацию о существовании друг друга. Объекты-коллеги могут хранить ссылки друг на друга, но это усиливает степень связанности системы. При высокой связанности каждому объекту пришлось бы иметь информацию обо всех остальных. Некоторые из шаблонов решают эту проблему.
Цепочка обязанностей (Chain of responsibility)
Поведенческий шаблон проектирования, предназначенный для организации в системе уровней ответственности.
Применение
Шаблон рекомендован для использования в условиях:
в разрабатываемой системе имеется группа объектов, которые могут обрабатывать сообщения определенного типа;
все сообщения должны быть обработаны хотя бы одним объектом системы;
сообщения в системе обрабатываются по схеме «обработай сам либо перешли другому», то есть одни сообщения обрабатываются на том уровне, где они получены, а другие пересылаются объектам иного уровня.
Пример использования
using System; namespace DoFactory.GangOfFour.Chain.Structural { class MainApp { static void Main() { // Setup Chain of Responsibility Handler h1 = new ConcreteHandler1(); Handler h2 = new ConcreteHandler2(); Handler h3 = new ConcreteHandler3(); h1.SetSuccessor(h2); h2.SetSuccessor(h3); // Generate and process request int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 }; foreach (int request in requests) { h1.HandleRequest(request); } // Wait for user Console.ReadKey(); } } abstract class Handler { protected Handler successor; public void SetSuccessor(Handler successor) { this.successor = successor; } public abstract void HandleRequest(int request); } class ConcreteHandler1 : Handler { public override void HandleRequest(int request) { if (request >= 0 && request < 10) { Console.WriteLine("{0} handled request {1}", this.GetType().Name, request); } else if (successor != null) { successor.HandleRequest(request); } } } class ConcreteHandler2 : Handler { public override void HandleRequest(int request) { if (request >= 10 && request < 20) { Console.WriteLine("{0} handled request {1}", this.GetType().Name, request); } else if (successor != null) { successor.HandleRequest(request); } } } class ConcreteHandler3 : Handler { public override void HandleRequest(int request) { if (request >= 20 && request < 30) { Console.WriteLine("{0} handled request {1}", this.GetType().Name, request); } else if (successor != null) { successor.HandleRequest(request); } } } }
Команда (Command, Action)
Шаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры.
Цель
Создание структуры, в которой класс-отправитель и класс-получатель не зависят друг от друга напрямую. Организация обратного вызова к классу, который включает в себя класс-отправитель.
Описание
Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект. Например, библиотека печати может иметь класс PrintJob. Для его использования можно создать объект PrintJob, установить необходимые параметры, и вызвать метод, непосредственно отсылающий задание на печать.
Пример использования
using System; using System.Collections.Generic;
namespace Command { class MainApp { static void Main() { User user = new User(); user.Compute('+', 100); user.Compute('-', 50); user.Compute('*', 10); user.Compute('/', 2); user.Undo(4); user.Redo(3); Console.Read(); } } // "Command" abstract class Command { public abstract void Execute(); public abstract void UnExecute(); } // "ConcreteCommand" class CalculatorCommand : Command { char @operator; int operand; Calculator calculator; // Constructor public CalculatorCommand(Calculator calculator, char @operator, int operand) { this.calculator = calculator; this.@operator = @operator; this.operand = operand; } public char Operator { set{ @operator = value; } } public int Operand { set{ operand = value; } } public override void Execute() { calculator.Operation(@operator, operand); } public override void UnExecute() { calculator.Operation(Undo(@operator), operand); } // Private helper function private char Undo(char @operator) { char undo; switch(@operator) { case '+': undo = '-'; break; case '-': undo = '+'; break; case '*': undo = '/'; break; case '/': undo = '*'; break; default : undo = ' '; break; } return undo; } } // "Receiver" class Calculator { private int curr = 0; public void Operation(char @operator, int operand) { switch(@operator) { case '+': curr += operand; break; case '-': curr -= operand; break; case '*': curr *= operand; break; case '/': curr /= operand; break; } Console.WriteLine("Current value = {0,3} (following {1} {2})", curr, @operator, operand); } } // "Invoker" class User { // Initializers private Calculator _calculator = new Calculator(); private List _commands = new List(); private int _current = 0; public void Redo(int levels) { Console.WriteLine("\n---- Redo {0} levels ", levels); for (int i = 0; i < levels; i++) if (_current < _commands.Count - 1) _commands[_current++].Execute(); } public void Undo(int levels) { Console.WriteLine("\n---- Undo {0} levels ", levels); // Делаем отмену операций for (int i = 0; i < levels; i++) if (_current > 0) _commands[--_current].UnExecute(); } public void Compute(char @operator, int operand) { Command command = new CalculatorCommand(_calculator, @operator, operand); command.Execute(); _commands.Add(command); _current++; } } }
Интерпретатор (Interpreter)
Шаблон проектирования, решающий часто встречающуюся, но подверженную изменениям, задачу. Также известен как Little (Small) Language.
Задача поиска строк по образцу может быть решена посредством создания интерпретатора, определяющего грамматику языка. "Клиент" строит предложение в виде абстрактного синтаксического дерева, в узлах которого находятся объекты классов "НетерминальноеВыражение" и "ТерминальноеВыражение" (рекурсивное), затем "Клиент" инициализирует контекст и вызывает операцию Разобрать(Контекст). На каждом узле типа "НетерминальноеВыражение" определяется операция Разобрать для каждого подвыражения. Для класса "ТерминальноеВыражение" операция Разобрать определяет базу рекурсии. "АбстрактноеВыражение" определяет абстрактную операцию Разобрать, общую для всех узлов в абстрактном синтаксическом дереве. "Контекст" содержит информацию, глобальную по отношению к интерпретатору.
Проблема
Имеется часто встречающаяся, подверженная изменениям задача.
Решение
Создать интерпретатор, который решает данную задачу.
Преимущества
Грамматику становится легко расширять и изменять, реализации классов, описывающих узлы абстрактного синтаксического дерева похожи (легко кодируются). Можно легко изменять способ вычисления выражений.
Недостатки
Сопровождение грамматики с большим числом правил затруднительно.
Пример использования
using System; using System.Collections; namespace Interpreter { class MainApp { static void Main() { Context context = new Context(); // Usually a tree ArrayList list = new ArrayList(); // Populate 'abstract syntax tree' list.Add(new TerminalExpression()); list.Add(new NonterminalExpression()); list.Add(new TerminalExpression()); list.Add(new TerminalExpression()); // Interpret foreach (AbstractExpression exp in list) { exp.Interpret(context); } // Wait for user Console.ReadKey(); } } class Context { } abstract class AbstractExpression { public abstract void Interpret(Context context); } class TerminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Called Terminal.Interpret()"); } } class NonterminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Called Nonterminal.Interpret()"); } } }
Итератор (Iterator, Cursor)
Представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из объектов, входящий в состав агрегации.
Например, такие элементы как дерево, связанный список, хэш-таблица и массив могут быть пролистаны (и модифицированы) с помощью паттерна (объекта) Итератор.
Перебор элементов выполняется объектом итератора, а не самой коллекцией. Это упрощает интерфейс и реализацию коллекции, а также способствует более логичному распределению обязанностей.
Особенностью полноценно реализованного итератора является то, что код, использующий итератор, может ничего не знать о типе итерируемого агрегата.
Конечно же, почти любой агрегат можно итерировать указателем void*, но при этом:
не ясно, что является значением «конец агрегата», для двусвязного списка это &ListHead, для массива это &array[size], для односвязного списка это NULL;
операция Next сильно зависит от типа агрегата.
Итераторы абстрагируют именно эти 2 проблемы, используя полиморфный Next (часто реализованный как operator++ в С++) и полиморфный aggregate.end(), возвращающий значение «конец агрегата».
Таким образом, появляется возможность работы с диапазонами итераторов, при отсутствии знания о типе итерируемого агрегата.
Пример использования
using System; using System.Collections; namespace Iterator { class MainApp { static void Main() { ConcreteAggregate a = new ConcreteAggregate(); a[0] = "Item A"; a[1] = "Item B"; a[2] = "Item C"; a[3] = "Item D"; // Create Iterator and provide aggregate ConcreteIterator i = new ConcreteIterator(a); Console.WriteLine("Iterating over collection:"); object item = i.First(); while (item != null) { Console.WriteLine(item); item = i.Next(); } // Wait for user Console.ReadKey(); } } abstract class Aggregate { public abstract Iterator CreateIterator(); public abstract int Count { get; protected set; } public abstract object this[int index] { get; set; } } class ConcreteAggregate : Aggregate { private readonly ArrayList _items = new ArrayList(); public override Iterator CreateIterator() { return new ConcreteIterator(this); } public override int Count { get { return _items.Count; } protected set { } } public override object this[int index] { get { return _items[index]; } set { _items.Insert(index, value); } } } abstract class Iterator { public abstract object First(); public abstract object Next(); public abstract bool IsDone(); public abstract object CurrentItem(); } class ConcreteIterator : Iterator { private readonly Aggregate _aggregate; private int _current; public ConcreteIterator(Aggregate aggregate) { this._aggregate = aggregate; } public override object First() { return _aggregate[0]; } public override object Next() { object ret = null; if (_current < _aggregate.Count - 1) { ret = _aggregate[++_current]; } return ret; } public override object CurrentItem() { return _aggregate[_current]; } public override bool IsDone() { return _current >= _aggregate.Count; } } }
Посредник (Mediator)
Обеспечивает взаимодействие множества объектов, формируя при этом слабую связанность и избавляя объекты от необходимости явно ссылаться друг на друга.
"Посредник" определяет интерфейс для обмена информацией с объектами "Коллеги", "Конкретный посредник" координирует действия объектов "Коллеги". Каждый класс "Коллеги" знает о своем объекте "Посредник", все "Коллеги" обмениваются информацией только с посредником, при его отсутствии им пришлось бы обмениваться информацией напрямую. "Коллеги" посылают запросы посреднику и получают запросы от него. "Посредник" реализует кооперативное поведение, пересылая каждый запрос одному или нескольким "Коллегам".
Проблема
Обеспечить взаимодействие множества объектов, сформировав при этом слабую связанность и избавив объекты от необходимости явно ссылаться друг на друга.
Решение
Создать объект, инкапсулирующий способ взаимодействия множества объектов.
Преимущества
Устраняется связанность между "Коллегами", централизуется управление.
Пример использования
using System; namespace Mediator { class MainApp { static void Main() { ConcreteMediator m = new ConcreteMediator(); ConcreteColleague1 c1 = new ConcreteColleague1(m); ConcreteColleague2 c2 = new ConcreteColleague2(m); m.Colleague1 = c1; m.Colleague2 = c2; c1.Send("How are you?"); c2.Send("Fine, thanks"); // Wait for user Console.ReadKey(); } } abstract class Mediator { public abstract void Send(string message, Colleague colleague); } class ConcreteMediator : Mediator { private ConcreteColleague1 _colleague1; private ConcreteColleague2 _colleague2; public ConcreteColleague1 Colleague1 { set { _colleague1 = value; } } public ConcreteColleague2 Colleague2 { set { _colleague2 = value; } } public override void Send(string message, Colleague colleague) { if (colleague == _colleague1) { _colleague2.Notify(message); } else { _colleague1.Notify(message); } } } abstract class Colleague { protected Mediator mediator; // Constructor public Colleague(Mediator mediator) { this.mediator = mediator; } } class ConcreteColleague1 : Colleague { // Constructor public ConcreteColleague1(Mediator mediator): base(mediator) { } public void Send(string message) { mediator.Send(message, this); } public void Notify(string message) { Console.WriteLine("Colleague1 gets message: " + message); } } class ConcreteColleague2 : Colleague { // Constructor public ConcreteColleague2(Mediator mediator): base(mediator) { } public void Send(string message) { mediator.Send(message, this); } public void Notify(string message) { Console.WriteLine("Colleague2 gets message: " + message); } } }
Хранитель (Token, Memento)
Позволяет, не нарушая инкапсуляцию, зафиксировать и сохранить внутреннее состояния объекта так, чтобы позднее восстановить его в этом состоянии. Существует два возможных варианта реализации данного шаблона: классический, описанный в книге Design Patterns, и реже встречаемый нестандартный вариант.
Классический вариант: Шаблон Хранитель используется двумя объектами: "Создателем" (originator) и "Опекуном" (caretaker). "Создатель" - это объект, у которого есть внутреннее состояние. Объект "Опекун" может производить некоторые действия с "Создателем", но при этом необходимо иметь возможность откатить изменения. Для этого "Опекун" запрашивает у "Создателя" объект "Хранителя". Затем выполняет запланированное действие (или последовательность действий). Для выполнения отката "Создателя" к состоянию, которое предшествовало изменениям, "Опекун" возвращает объект "Хранителя" его "Создателю". "Хранитель" является непрозрачным (т.е. таким, который не может или не должен изменяться "Опекуном").
Нестандартный вариант: Отличие данного варианта от классического заключено в более жёстком ограничении на доступ "Опекуна" к внутреннему состоянию "Создателя". В классическом варианте у "Опекуна" есть потенциальная возможность получить доступ к внутренним данным "Создателя" через "Хранителя", изменить состояние и установить его обратно "Создателю". В данном варианте "Опекун" обладает возможностью лишь восстановить состояние "Хранителя", вызвав Restore. Кроме всего прочего, "Опекуну" не требуется владеть связью на "Хранителя", чтобы восстановить его состояние. Это позволяет сохранять и восстанавливать состояние сложных иерархических или сетевых структур (состояния объектов и всех связей между ними) путём сбора снимков всех зарегистрированных объектов системы.
Пример использования
using System; namespace MementoPatte { class Program { static void Main(string[] args) { Foo foo = new Foo("Test", 15); foo.Print(); Caretaker ct1 = new Caretaker(); Caretaker ct2 = new Caretaker(); ct1.SaveState(foo); foo.IntProperty += 152; foo.Print(); ct2.SaveState(foo); ct1.RestoreState(foo); foo.Print(); ct2.RestoreState(foo); foo.Print(); Console.ReadKey(); } } public interface IOriginator { object GetMemento(); void SetMemento(object memento); } public class Foo : IOriginator { public string StringProperty { get; private set; } public int IntProperty { get; set; } public Foo(string stringPropertyValue, int intPropertyValue = 0) { StringProperty = stringPropertyValue; IntProperty = intPropertyValue; } public void Print() { Console.WriteLine("============="); Console.WriteLine("StringProperty value: {0}",StringProperty); Console.WriteLine("IntProperty value: {0}",IntProperty); Console.WriteLine("============="); } object IOriginator.GetMemento() { return new Memento { StringProperty = this.StringProperty, IntProperty = this.IntProperty }; } void IOriginator.SetMemento(object memento) { if (Object.ReferenceEquals(memento, null)) throw new ArgumentNullException("memento"); if (!(memento is Memento)) throw new ArgumentException("memento"); StringProperty = ((Memento)memento).StringProperty; IntProperty = ((Memento)memento).IntProperty; } class Memento { public string StringProperty { get; set; } public int IntProperty { get; set; } } } public class Caretaker { private object m_memento; public void SaveState(IOriginator originator) { if (originator == null) throw new ArgumentNullException("originator"); m_memento = originator.GetMemento(); } public void RestoreState(IOriginator originator) { if (originator == null) throw new ArgumentNullException("originator"); if (m_memento == null) throw new InvalidOperationException("m_memento == null"); originator.SetMemento(m_memento); } } }
Нестандартный вариант
public interface IOriginator { IMemento GetState(); } public interface IShape : IOriginator { void Draw(); void Scale(double scale); void Move(double dx, double dy); } public interface IMemento { void RestoreState(); } public class CircleOriginator : IShape { private class CircleMemento : IMemento { private readonly double x; private readonly double y; private readonly double r; private readonly CircleOriginator originator; public CircleMemento(CircleOriginator originator) { this.originator = originator; x = originator.x; y = originator.y; r = originator.r; } public void Restore() { originator.x = x; originator.y = y; originator.r = r; } } double x; double y; double r; public void CircleOriginator(double x, double y, double r) { this.x = x; this.y = y; this.r = r; } public void Draw() { Console.WriteLine("Circle with radius {0} at ({1}, {2})", r, x, y); } public void Scale(double scale) { r *= scale; } public void Move(double dx, double dy) { x += dx; y += dy; } public CircleMemento GetState() { return new CircleMemento(this); } } public class RectOriginator : IShape { private class RectMemento : IMemento { private readonly double x; private readonly double y; private readonly double w; private readonly double h; private readonly RectOriginator originator; public RectMemento(RectOriginator originator) { this.originator = originator; x = originator.x; y = originator.y; w = originator.w; h = originator.h; } public void Restore() { originator.x = x; originator.y = y; originator.w = w; originator.h = h; } } double x; double y; double w; double h; public void RectOriginator(double x, double y, double w, double h) { this.x = x; this.y = y; this.w = w; this.h = h; } public void Draw() { Console.WriteLine("Rectangle {0}x{1} at ({2}, {3})", w, h, x, y); } public void Scale(double scale) { w *= scale; h *= scale; } public void Move(double dx, double dy) { x += dx; y += dy; } public IMemento GetState() { return new RectMemento(this); } } public class Caretaker { public void Draw(IEnumerable shapes) { foreach(IShape shape in shapes) { shape.Draw(); } } public void MoveAndScale(IEnumerable shapes) { foreach(IShape shape in shapes) { shape.Scale(10); shape.Move(3, 2); } } public IEnumerable SaveStates(IEnumerable shapes) { List states = new List(); foreach(IShape shape in shapes) { states.Add(shape.GetState()); } } public void RestoreStates(IEnumerable states) { foreach(IMemento state in states) { state.Restore(); } } public static void Main() { IShape[] shapes = { new RectOriginator(10, 20, 3, 5), new CircleOriginator(5, 2, 10) }; // Rectangle 3x5 at (10, 20) // Circle with radius 10 at (5, 2) Draw(shapes); IEnumerable states = SaveStates(shapes); MoveAndScale(shapes); // Rectangle 30x50 at (13, 22) // Circle with radius 100 at (8, 4) Draw(shapes); RestoreStates(states); // Rectangle 3x5 at (10, 20) // Circle with radius 10 at (5, 2) Draw(shapes); } }
Наблюдатель (Observer, Dependents, Publisher-Subscriber)
Определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом событии.
Шаблон «наблюдатель» применяется в тех случаях, когда система обладает следующими свойствами:
существует, как минимум, один объект, рассылающий сообщения;
имеется не менее одного получателя сообщений, причём их количество и состав могут изменяться во время работы приложения;
нет надобности очень сильно связывать взаимодействующие объекты, что полезно для повторного использования.
Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают получатели с предоставленной им информацией.
Пример использования
using System; namespace Observer { class Program { static void Main(string[] args) { Subject subject = new Subject(); Observer Observer = new Observer(subject,"Center","\t\t"); Observer observer2 = new Observer(subject,"Right","\t\t\t\t"); subject.Go(); // Wait for user Console.Read(); } } class Simulator : IEnumerable { string [] moves = {"5","3","1","6","7"}; public IEnumerator GetEnumerator() { foreach( string element in moves ) yield return element; } } class Subject { public delegate void Callback (string s); public event Callback Notify; Simulator simulator = new Simulator( ); const int speed = 200; public string SubjectState { get; set; } public void Go() { new Thread(new ThreadStart(Run)).Start( ); } void Run () { foreach (string s in simulator) { Console.WriteLine("Subject: " + s); SubjectState = s; Notify(s); Thread.Sleep(speed); // milliseconds } } } interface IObserver { void Update(string state); } class Observer : IObserver { string name; Subject subject; string state; string gap; public Observer(Subject subject, string name, string gap) { this.subject = subject; this.name = name; this.gap = gap; subject.Notify += Update; } public void Update(string subjectState) { state = subjectState; Console.WriteLine(gap + name + ": " + state); } } }
Состояние (State)
Используется в тех случаях, когда во время выполнения программы объект должен менять свое поведение в зависимости от своего состояния.
Паттерн состоит из 3 блоков:
Widget
Класс, объекты которого должны менять свое поведение в зависимости от состояния.
IState
Интерфейс, который должен реализовать каждое из конкретных состояний. Через этот интерфейс объект Widget взаимодействует с состоянием, делегируя ему вызовы методов. Интерфейс должен содержать средства для обратной связи с объектом, поведение которого нужно изменить. Для этого используется событие (паттерн Publisher — Subscriber). Это необходимо для того, чтобы в процессе выполнения программы заменять объект состояния при появлении событий. Возможны случаи, когда сам Widget периодически опрашивает объект состояние на наличие перехода.
StateA … StateZ
Классы конкретных состояний. Должны содержать информацию о том, при каких условиях и в какие состояния может переходить объект из текущего состояния. Например, из StateA объект может переходить в состояние StateB и StateC, а из StateB — обратно в StateA и так далее. Объект одного из них должен содержать Widget при создании.
Пример использования
using System; namespace State { public interface IAutomatState { String GotApplication(); String CheckApplication(); String RentApartment(); String DispenseKeys(); } public interface IAutomat { void GotApplication(); void CheckApplication(); void RentApartment(); void SetState(IAutomatState s); IAutomatState GetWaitingState(); IAutomatState GetGotApplicationState(); IAutomatState GetApartmentRentedState(); IAutomatState GetFullyRentedState(); Int32 Count { get; set; } } public class Automat : IAutomat { private IAutomatState _waitingState; private IAutomatState _gotApplicationState; private IAutomatState _apartmentRentedState; private IAutomatState _fullyRentedState; private IAutomatState _state; private Int32 _count; public Automat(Int32 n) { _count = n; _waitingState = new WaitingState(this); _gotApplicationState = new GotApplicationState(this); _apartmentRentedState = new ApartmentRentedState(this); _fullyRentedState = new FullyRentedState(this); _state = _waitingState; } public void GotApplication() { Console.WriteLine(_state.GotApplication()); } public void CheckApplication() { Console.WriteLine(_state.CheckApplication()); } public void RentApartment() { Console.WriteLine(_state.RentApartment()); Console.WriteLine(_state.DispenseKeys()); } public void SetState(IAutomatState s) { _state = s; } public IAutomatState GetWaitingState() { return _waitingState; } public IAutomatState GetGotApplicationState() { return _gotApplicationState; } public IAutomatState GetApartmentRentedState() { return _apartmentRentedState; } public IAutomatState GetFullyRentedState() { return _fullyRentedState; } public int Count { get { return _count; } set { _count = value; } } } public class WaitingState : IAutomatState { private Automat _automat; public WaitingState(Automat automat) { _automat = automat; } public String GotApplication() { _automat.SetState(_automat.GetGotApplicationState()); return "Thanks for the application."; } public String CheckApplication() { return "You have to submit an application."; } public String RentApartment() { return "You have to submit an application."; } public String DispenseKeys() { return "You have to submit an application."; } } public class GotApplicationState : IAutomatState { private Automat _automat; private readonly Random _random; public GotApplicationState(Automat automat) { _automat = automat; _random = new Random(System.DateTime.Now.Millisecond); } public String GotApplication() { return "We already got your application."; } public String CheckApplication() { var yesNo = _random.Next() % 10; if (yesNo > 4 && _automat.Count > 0) { _automat.SetState(_automat.GetApartmentRentedState()); return "Congratulations, you were approved."; } else { _automat.SetState(_automat.GetWaitingState()); return "Sorry, you were not approved."; } } public String RentApartment() { return "You must have your application checked."; } public String DispenseKeys() { return "You must have your application checked."; } } public class ApartmentRentedState : IAutomatState { private Automat _automat; public ApartmentRentedState(Automat automat) { _automat = automat; } public String GotApplication() { return "Hang on, we'ra renting you an apartmeny."; } public String CheckApplication() { return "Hang on, we'ra renting you an apartmeny."; } public String RentApartment() { _automat.Count = _automat.Count - 1; return "Renting you an apartment...."; } public String DispenseKeys() { if(_automat.Count <= 0) _automat.SetState(_automat.GetFullyRentedState()); else _automat.SetState(_automat.GetWaitingState()); return "Here are your keys!"; } } public class FullyRentedState : IAutomatState { private Automat _automat; public FullyRentedState(Automat automat) { _automat = automat; } public String GotApplication() { return "Sorry, we're fully rented."; } public String CheckApplication() { return "Sorry, we're fully rented."; } public String RentApartment() { return "Sorry, we're fully rented."; } public String DispenseKeys() { return "Sorry, we're fully rented."; } } class Program { static void Main(string[] args) { var automat = new Automat(9); automat.GotApplication(); automat.CheckApplication(); automat.RentApartment(); } } }
Тоже самое, но так делать плохо
using System; namespace State { public enum State { FULLY_RENTED = 0, WAITING = 1, GOT_APPLICATION = 2, APARTMENT_RENTED = 3, } public class RentalMethods { private readonly Random _random; private Int32 _numberApartments; private State _state = State.WAITING; public RentalMethods(Int32 n) { _numberApartments = n; _random = new Random(System.DateTime.Now.Millisecond); } public void GetApplication() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: _state = State.GOT_APPLICATION; Console.WriteLine("Thanks for the application."); break; case State.GOT_APPLICATION: Console.WriteLine("We already got your application."); break; case State.APARTMENT_RENTED: Console.WriteLine("Hang on, we'ra renting you an apartmeny."); break; } } public void CheckApplication() { var yesNo = _random.Next()%10; switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: if (yesNo > 4 && _numberApartments > 0) { Console.WriteLine("Congratulations, you were approved."); _state = State.APARTMENT_RENTED; RentApartment(); } else { Console.WriteLine("Sorry, you were not approved."); _state = State.WAITING; } break; case State.APARTMENT_RENTED: Console.WriteLine("Hang on, we'ra renting you an apartmeny."); break; } } public void RentApartment() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: Console.WriteLine("You must have your application checked."); break; case State.APARTMENT_RENTED: Console.WriteLine("Renting you an apartment...."); _numberApartments--; DispenseKeys(); break; } } public void DispenseKeys() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: Console.WriteLine("You must have your application checked."); break; case State.APARTMENT_RENTED: Console.WriteLine("Here are your keys!"); _state = State.WAITING; break; } } } class Program { static void Main(string[] args) { var rentalMethods = new RentalMethods(9); rentalMethods.GetApplication(); rentalMethods.CheckApplication(); rentalMethods.RentApartment(); rentalMethods.DispenseKeys(); } } }
Стратегия (Strategy)
Шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путем определения соответствующего класса. Шаблон Strategy позволяет менять выбранный алгоритм независимо от объектов-клиентов, которые его используют.
Задача
По типу клиента (или по типу обрабатываемых данных) выбрать подходящий алгоритм, который следует применить. Если используется правило, которое не подвержено изменениям, нет необходимости обращаться к шаблону «стратегия».
Мотивы
Программа должна обеспечивать различные варианты алгоритма или поведения
Нужно изменять поведение каждого экземпляра класса
Необходимо изменять поведение объектов на стадии выполнения
Введение интерфейса позволяет классам-клиентам ничего не знать о классах, реализующих этот интерфейс и инкапсулирующих в себе конкретные алгоритмы
Решение
Отделение процедуры выбора алгоритма от его реализации. Это позволяет сделать выбор на основании контекста.
Класс Strategy определяет, как будут использоваться различные алгоритмы.
Конкретные классы ConcreteStrategy реализуют эти различные алгоритмы.
Класс Context использует конкретные классы ConcreteStrategy посредством ссылки на конкретный тип абстрактного класса Strategy. Классы Strategy и Context взаимодействуют с целью реализации выбранного алгоритма (в некоторых случаях классу Strategy требуется посылать запросы классу Context). Класс Context пересылает классу Strategy запрос, поступивший от его класса-клиента.
Следствия
Шаблон Strategy определяет семейство алгоритмов.
Это позволяет отказаться от использования переключателей и/или условных операторов.
Вызов всех алгоритмов должен осуществляться стандартным образом (все они должны иметь одинаковый интерфейс).
Реализация
Класс, который использует алгоритм (Context), включает абстрактный класс (Strategy), обладающий абстрактным методом, определяющим способ вызова алгоритма. Каждый производный класс реализует один требуемый вариант алгоритма. Замечание: метод вызова алгоритма не должен быть абстрактным, если требуется реализовать некоторое поведение, принимаемое по умолчанию. И стратегия, и декоратор может применяться для изменения поведения конкретных классов. Достоинство стратегии в том, что интерфейс кастомизации не совпадает с публичным интерфейсом и может быть куда более удобным, а недостаток в том, что для использования стратегии необходимо изначально проектировать класс с возможностью регистрации стратегий.
Использование
Архитектура Microsoft WDF основана на этом паттерне. У каждого объекта "драйвер" и "устройство" есть неизменяемая часть, вшитая в систему, в которой регистрируется изменяемая часть (стратегия), написанная в конкретной реализации. Изменяемая часть может быть и вовсе пустой, что даст ничего не делающий драйвер, но при этом способный участвовать в PnP и управлении питанием. Библиотека ATL содержит в себе набор классов threading model, которые являются стратегиями (различными реализациями Lock/Unlock, которые потом используются основными классами системы). При этом в этих стратегиях используется статический полиморфизм через параметр шаблона, а не динамический полиморфизм через виртуальные методы.
Пример использования
using System; namespace DesignPatterns.Behavioral.Strategy { public interface IStrategy { void Algorithm(); } public class ConcreteStrategy1 : IStrategy { public void Algorithm() { Console.WriteLine("Выполняется алгоритм стратегии 1."); } } public class ConcreteStrategy2 : IStrategy { public void Algorithm() { Console.WriteLine("Выполняется алгоритм стратегии 2."); } } public class Context { private IStrategy _strategy; public Context(IStrategy strategy) { _strategy = strategy; } public void SetStrategy(IStrategy strategy) { _strategy = strategy; } public void ExecuteOperation() { _strategy.Algorithm(); } } public static class Program { public static void Main() { Context context = new Context(new ConcreteStrategy1()); context.ExecuteOperation(); context.SetStrategy(new ConcreteStrategy2()); context.ExecuteOperation(); } } }
Шаблонный метод (Template method)
Шаблон проектирования, определяющий основу алгоритма и позволяющий наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
Применимость
Однократное использование инвариантной части алгоритма, с оставлением изменяющейся части на усмотрение наследникам.
Локализация и вычленение общего для нескольких классов кода для избегания дублирования.
Разрешение расширения кода наследниками только в определенных местах.
Участники
Abstract class (абстрактный класс) - определяет абстрактные операции, замещаемые в наследниках для реализации шагов алгоритма; реализует шаблонный метод, определяющий скелет алгоритма. Шаблонный метод вызывает замещаемые и другие, определенные в Abstract class, операции. Concrete class (конкретный класс) - реализует замещаемые операции необходимым для данной реализации способом. Concrete class предполагает, что инвариантные шаги алгоритма будут выполнены в AbstractClass.
Пример использования
namespace TemplateMethod { class TemplateMethodPattern { internal abstract class GameObject { protected int PlayersCount; abstract protected void InitializeGame(); abstract protected void MakePlay(int player); abstract protected bool EndOfGame(); abstract protected void PrintWinner(); // A template method public void PlayOneGame(int playersCount) { PlayersCount = playersCount; InitializeGame(); var j = 0; while (!EndOfGame()) { MakePlay(j); j = (j + 1) % playersCount; } PrintWinner(); } } //Now we can extend this class in order to implement actual games: public class Monopoly : GameObject { protected override void InitializeGame() { // Initialize money } protected override void MakePlay(int player) { // Process one turn of player } protected override bool EndOfGame() { return true; } protected override void PrintWinner() { // Display who won } } public class Chess : GameObject { protected override void InitializeGame() { // Put the pieces on the board } protected override void MakePlay(int player) { // Process a turn for the player } protected override bool EndOfGame() { return true; } protected override void PrintWinner() { // Display the winning player } } public static void Test() { GameObject game = new Monopoly(); game.PlayOneGame(2); } } }
Посетитель (Visitor)
Описывает операцию, которая выполняется над объектами других классов. При изменении Visitor нет необходимости изменять обслуживаемые классы.
Проблема
Над каждым объектом некоторой структуры выполняется одна или более операций. Определить новую операцию, не изменяя классы объектов.
Решение
Для полной независимости посетители имеют отдельную от обслуживаемых структур иерархию. Структуры должны иметь некий интерфейс взаимодействия. При необходимости добавления новых операций необходимо создать новый класс ConcreteVisitor и поместить его в цепочку обхода обслуживаемых структур.
Рекомендации
Шаблон «Посетитель» следует использовать, если:
в структуре присутствуют объекты разных классов с различными интерфейсами, и необходимо выполнить над ними операции, зависящие от конкретных классов.
если над обслуживаемой структурой надо выполнять самые различные, порой не связанные между собой операции. То есть они усложняют эту структуру.
часто добавляются новые операции над обслуживаемой структурой.
реализация double dispatch. Концептуально это нечто вроде {a; b} -> method(params), где реально вызываемый по стрелочке метод зависит как от типа a, так и от типа b. Так как большинство объектно-ориентированных языков программирования не поддерживает такое на уровне синтаксиса, для такого обычно применяется Visitor в виде a -> visit(b, params), который в свою очередь вызывает b -> visitA(a, params), что дает выбор и по типу a, и по типу b.
Преимущества
упрощается добавление новых операций
объединяет родственные операции в классе «Посетитель».
экземпляр визитора может иметь в себе состояние (например, общую сумму) и накапливать его по ходу обхода контейнера.
Недостатки
затруднено добавление новых классов, поскольку требуется объявление новой абстрактной операции в интерфейсе визитора, а значит — и во всех классах, реализующих данный интерфейс.