Паттерн Декоратор (Decorator)Паттерн Декоратор (Decorator)

Презентация:



Advertisements
Похожие презентации
©Павловская Т.А. (СПбГУ ИТМО) Курс «С#. Программирование на языке высокого уровня» Павловская Т.А.
Advertisements

Перегрузка операторов x = a + b результат 1-й операнд2-й операнд оператор По количеству операндов операторы делятся на: унарные (один операнд) бинарные.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
ORM Паттерны. Repository Repository (хранилище) выступает в роли посредника между слоем домена и слоем отображения данных, предоставляя интерфейс в виде.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Полиморфизм Полиморфизм (polymorphism) - последний из трех "китов", на которых держится объектно-ориентированное программирование Слово это можно перевести.
Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ.
Основы ООП и C# Работа с объектами и классами. Классы Класс специальный тип данных для описания объектов. Он определяет данные и поведение типа. Определение.
Особенности C# Индексаторы, события, частичные методы, расширяющие методы, сборщик мусора DraggonZ.
ИТЕРАТОРЫ И LINQ Лекция 1. Интерфейс IEnumerable и IEnumerator Любая коллекция реализует интерфейс IEnumerable. public interface IEnumerable : IEnumerable.
Информационные технологии Стандартные библиотечные функции манипулирование данными преобразование и шифрование определение пользователями функций.
1 Диаграммы реализации (implementation diagrams).
Делегаты Как созданные объекты могут посылать сообщения тем объектам, которые их породили? При программировании под Windows на С и C++ основное средство.
Методология объектно- ориентированного программирования.
Тема 5. Основы современной технологии программирования Программирование в средах современных информационных систем. Интегрированные системы разработки.
Объектно-ориентированный подход в языке C#. Класс в языке C# - ссылочный тип, определенный пользователем. Для классов ЯП C# допустимо только единичное.
События События Важная роль делегатов заключается в том, что на них основана модель событий С#. Применение событий вовсе не ограничено приложениями с графическим.
ДЕЛЕГАТЫ Лекция 7 1. Зачем нужны делегаты 2 И данные, и код располагаются в памяти компьютера по определенным адресам. Передача адресов данных в C# происходит.
Наследование и полиморфизм. «Быть» или «Иметь» а так же «Точно» или «Как получится»
Транксрипт:

Паттерн Декоратор (Decorator)

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

Диаграмма классов

Участники Component компонент. Задает интерфейс для объектов, на которые впоследствии могут быть динамически возложены дополнительные обязанности. ConcreteComponent конкретный компонент. Определяет объект, на который возлагаются дополнительные обязанности. Decorator декоратор. Хранит ссылку на объект Component и наследует реализацию его интерфейса по умолчанию. ConcreteDecorator конкретный декоратор. Возлагает дополнительные обязанности на компонент.

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

Пример 1 Классический пример добавление рамок к произвольному глифу. Рассмотрим интерфейс, которым обладает глиф: public interface IGlyph { void Draw(); }

Пример 1 Определим класс декоратора глифа, который будет предком всех декораторов: public abstract class GlyphDecorator : IGlyph { protected GlyphDecorator(IGlyph innerGlyph) { Debug.Assert(innerGlyph != null); InnerGlyph = innerGlyph; } protected IGlyph InnerGlyph { get; set; } public virtual void Draw() { InnerGlyph.Draw(); }}}}

Пример 1 Определим класс декоратора, который дорисовывает рамку. Также он добавляет дополнительное состояние: толщину и цвет рамки: public class BorderDecorator : GlyphDecorator { public BorderDecorator(IGlyph innerGlyph, Color borderColor, int borderWidth) : base(innerGlyph) { BorderColor = borderColor; BorderWidth = borderWidth; } public Color BorderColor { get; set; } public int BorderWidth { get; set; } public override void Draw() { DrawBorder(); base.Draw(); }}}}

Пример 1 Для того чтобы нарисовать рамку красного цвета толщиной в один пиксель вокруг изображения, необходим следующий код: IGlyph glyph = new BorderDecorator(new ImageGlyph("file"), Color.Red, 1); glyph.Draw(); Декораторы можно комбинировать произвольным образом, например, чтобы нарисовать рамку красного, а потом черного цвета, необходим код такого вида: IGlyph glyph = new BorderDecorator( new BorderDecorator( new ImageGlyph("file"), Color.Red, 1), Color.Black, 1); glyph.Draw();

Пример 1 Другим примером декоратора глифа мог бы быть декоратор, который помещает глиф в ограниченную область и добавляет полосы прокрутки.

Пример 2 Рассмотрим использование паттерна на примере одной из возможных реализаций xUnit. Ключевым интерфейсом библиотеки является интерфейс ITest с методом Run, который запускает выполнение теста. Класс TestCase представляет собой один тестовый метод. Для определения набора тестов используется класс TestSuite.

Пример 2

Для создания набора тестовых методов необходимо создать наследник класса TestCase и определить в нм тестовые методы. Затем для каждого тестового метода будет создан экземпляр этого класса, который будет добавлен в класс TestSuite. Методы SetUp и TearDown будут выполнены для каждого тестового метода.

Пример 2 Ниже приведн код метода, который собирает набор тестов (TestSuite) из всех тестовых методов, определнных в конкретном наследника TestCase: public class TestCase : ITest { public static ITest Suite() { TestSuite result = new TestSuite(); foreach(MethodInfo method in GetTestMethods()) result.AddTest(new TestCase(method)); return result; }}}}

Пример 2 Перед выполнением всего набора тестов необходимо выполнить инициализацию. Создадим декоратор, который будет выполнять некоторые действия перед выполнением всего набора тестов. Базовый класс для всех декораторов: public class TestDecorator : ITest { private ITest test; public TestDecorator(ITest test) { this.test = test; } public virtual void Run() { test.Run(); }}}}

Пример 2 Декоратор, который добавляет выполнение некоторых действий до и после выполнения теста: public class TestSetupDecorator : TestDecorator { public TestSetupDecorator(ITest test) : base(test) { } public virtual void SetUp() { } public virtual void TearDown() { } public override void Run() { SetUp(); base.Run(); TearDown(); }}}}

Пример 3 Рассмотрим ещ один пример декоратора: декоратор событий (Event Decorator). Декораторы данного типа добавляют возможность уведомления о вызове тех или иных методов. Пусть есть интерфейс, определяющий подключение к базе данных: public interface IConnection { void Connect(); void Disconnect(); void ExecuteQuery(string sql); }

Пример 3 Базовый класс для всех декораторов будет следующим: public class ConnectionDecorator : IConnection { private IConnection innerConnection; public ConnectionDecorator(IConnection innerConnection) { this.innerConnection = innerConnection; } public virtual void Connect() { innerConnection.Connect(); } public virtual void Disconnect() { innerConnection.Disconnect(); } public virtual void ExecuteQuery(string sql) { innerConnection.ExecuteQuery(sql); }}}}

Пример 3 Декоратор событий добавляет уведомления о выполнении того или иного метода: public class ConnectionEventDecorator : ConnectionDecorator { public ConnectionEventDecorator(IConnection innerConnection) : base(innerConnection) { } public EventHandler BeforeConnect; public EventHandler AfterConnect; public override void Connect() { if (BeforeConnect != null) BeforeConnect(this, EventArgs.Empty); base.Connect(); if (AfterConnect != null) AfterConnect(this, EventArgs.Empty); }}}}

Пример 4 Примером декоратора является, например, класс GZipStream из библиотеки.NET: public class GZipStream : Stream { public GZipStream(Stream stream, CompressionMode compressionMode); public override int Read(byte[] array, int offset, int count); }

Пример 4 private static void CreateFile() { Stream stream = new FileStream("text.gzip", FileMode.OpenOrCreate); stream = new GZipStream(stream, CompressionMode.Compress); using (stream) { using (StreamWriter writer = new StreamWriter(stream)) { writer.WriteLine("Bill Gates"); writer.WriteLine("Linus Torvalds"); }}}}}} private static void ReadFile() { Stream stream = new FileStream("text.gzip", FileMode.Open); stream = new GZipStream(stream, CompressionMode.Decompress); using (stream) { using (StreamReader reader = new StreamReader(stream)) { while (!reader.EndOfStream) Console.WriteLine(reader.ReadLine()); }}}}}}

Пример 5 Рассмотрим следующую задачу: необходимо выполнить перенос данных из одного сервера баз данных в другой. При этом необходимо обеспечить возможность внести данные сразу в базу данных и/или сохранить скрипт переноса в файл.

Пример 5

Паттерн Команда (Command)

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

Мотивация Требуется параметризовать объекты выполняемым действием. В процедурном языке такую параметризацию можно выразить с помощью функции обратного вызова. Команды представляют собой объектно-ориентированную альтернативу функциям обратного вызова. Необходимо определять, ставить в очередь и выполнять запросы в разное время. Необходимо обеспечить возможность отмены операций. Операция Execute объекта Command может сохранить состояние, необходимое для отката действий, выполненных командой. В этом случае дополнительная операция Unexecute отменит действия, выполненные предшествующим обращением к Execute. Требуется поддержка протоколирования изменений. Дополнив интерфейс класса Command операциями сохранения и загрузки, можно вести протокол изменений во внешней памяти. Для восстановления после сбоя нужно загрузить сохраненные команды и повторно выполнить их. Необходимо структурировать систему на основе высокоуровневых операций, построенных из примитивных (транзакции).

Диаграмма классов

Участники Command команда. Объявляет интерфейс для выполнения операций. ConcreteCommand конкретная команда. Определяет связь между объектом-получателем Receiver и действием. Реализует операцию Execute путем вызова соответствующих операций объекта Receiver. Client клиент. Создает объекты класса ConcreteCommand и устанавливает их получателей Invoker инициатор. Обращается к команде для выполнения запроса. Receiver получатель. Располагает информацией о способах выполнения операций, необходимых для удовлетворения запроса. В роли получателя может выступать любой класс.

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

Пример 1 Рассмотрим использование данного паттерна на примере простого текстового редактора. Пусть у нас есть модель документа, которая обладает следующим интерфейсом: public class Document { public string Text { get; set; } public int CursorPosition { get; set; } public string GetSelectedText(); }

Пример 1 Определим интерфейс команды таким образом: public interface ICommand { void Execute(); }

Пример 1 Команда копирования в буфер обмена будет иметь такой вид public class CopyCommand : ICommand { private readonly Document document; public CopyCommand(Document document) { this.document = document; } public void Execute() { Clipboard.SetText(document.GetSelectedText()); }}}}

Пример 1 Команда вставки из буфера будет выглядеть так: public class PasteCommand : ICommand { private readonly Document document; public PasteCommand(Document document) { this.document = document; } public void Execute() { document.Text.Insert(document.CursorPosition, Clipboard.GetText()); }}}}

Пример 1 Команда сохранения документа в файл будет следующей: public class SaveCommand : ICommand { private readonly Document document; public SaveCommand(Document document) { this.document = document; } public void Execute() { string fileName; if (ShowSaveFileDialog(out fileName)) { File.WriteAllText(fileName, document.Text); }}}}}}

Пример 1 Команды могут быть привязаны к пунктам меню следующим образом: public MenuItem CreateCommandMenuItem(ICommand command, string caption) { MenuItem result = new MenuItem(); result.Click += delegate { command.Execute(); }; result.Text = caption; return result; } public void BuildContextMenu(ContextMenu menu, Document document) { PasteCommand pasteCommand = new PasteCommand(document); CompileCommand compileCommand = new CompileCommand(document); menu.MenuItems.Add(CreateCommandMenuItem(pasteCommand, "Paste")); menu.MenuItems.Add(CreateCommandMenuItem(compileCommand, "Compile")); }

Пример 1 Рассмотрим теперь как будет реализовываться отмена операций. Для начала потребуется добавить к интерфейсу команды метод UnExecute. Кроме того, некоторые команды исключают возможность отмены. Учтм это, определив метод IsReversible: public interface ICommand { void Execute(); void UnExecute(); bool IsReversible { get; } }

Пример 1 Далее определим очередь команд: public class CommandHistory { public void ExecuteCommand(ICommand command); public void Undo(); public void Redo(); } При вызове метода ExecuteCommand команда будет выполняться и помещаться в список. Затем при вызове метода Undo команды будут отменяться вызовом UnExecute одна за одной.

Пример 1 Первая реализация метода добавления будет следующей: public class CommandHistory { private List commands = new List (); public void ExecuteCommand(ICommand command) { command.Execute(); commands.Add(command); }}}}

Пример 1 Для реализации методов отмены и повтора нам потребуется понятие текущей операции. Заведм для этого поле CurrentPosition. Методы отмены и повтора операций будут выглядеть следующим образом: public class CommandHistory { private List commands = new List (); public int CurrentPosition { get; private set; } public void Undo() { Debug.Assert(CurrentPosition >= 0); commands[CurrentPosition].UnExecute(); CurrentPosition--; } public void Redo() { Debug.Assert(CurrentPosition >= -1); Debug.Assert(CurrentPosition < commands.Count); CurrentPosition++; commands[CurrentPosition].Execute(); }}}}

Пример 1 Исправим реализацию метода добавления команды, так, чтобы при добавлении новой команды история всех отменных операций стиралась: public class CommandHistory { private List commands = new List (); public int CurrentPosition { get; private set; } public void ExecuteCommand(ICommand command) { command.Execute(); for (int i = commands.Count - 1; i > CurrentPosition; i--) commands.RemoveAt(i); commands.Add(command); CurrentPosition++; }}}}

Пример 2 Команды также могут быть выполнены вместе в одной транзакции. Для этого требуется специальный тип команды составная команда, расширяющая интерфейс методами для конструирования команды. public class CompositeCommand : ICommand { private readonly List childCommands = new List (); public CompositeCommand(params ICommand[] childCommands) { this.childCommands.AddRange(childCommands); } public void AddCommand(ICommand command) { childCommands.Add(command); } public void RemoveCommand(ICommand command) { childCommands.Remove(command); }}}}

Пример 2 Реализация методов выполнения и отмены операций выглядит следующим образом: public class CompositeCommand : ICommand { private readonly List childCommands = new List (); public void Execute() { foreach (ICommand childCommand in childCommands) childCommand.Execute(); } public void UnExecute() { foreach (ICommand childCommand in childCommands.AsEnumerable().Reverse()) childCommand.UnExecute(); }}}}

Пример 2 Для обеспечения атомарности выполнения операции можно использовать следующую реализацию метода выполнения: public class CompositeCommand : ICommand { public void Execute() { List executedCommands = new List (); foreach (ICommand childCommand in childCommands) { try { childCommand.Execute(); executedCommands.Add(childCommand); } catch { foreach (ICommand executedCommand in executedCommands.AsEnumerable().Reverse()) executedCommand.UnExecute(); break; }}}}}}}}

Пример 2 Пусть наш документ представляет собой некоторый программный код, и перед его компиляцией требуется выполнить сохранение резервной копии документа. Для этого можно воспользоваться составной командой: Document document = new Document(); CompositeCommand compositeCommand = new CompositeCommand( new CreateBackupCommand(document), new CompileCommand(document) ); compositeCommand.Execute();

Пример 3 Библиотека WPF в составе.NET изначально поддерживаем механизм команд. Windows Presentation Foundation (WPF) система построения графических приложений Windows. В основе технологии лежит расширяемый язык разметки приложений XAML основанный на XML язык разметки для декларативного программирования приложений. Более подробно:

Пример 3 Библиотека WPF содержит следующий интерфейс: public interface ICommand { void Execute(object parameter); bool CanExecute(object parameter); event EventHandler CanExecuteChanged; }

Пример 3 Тогда команда вставки выглядела бы следующим образом: public class PasteCommand : ICommand { private readonly Document document; public PasteCommand(Document document) { this.document = document; } public void Execute(object parameter) { document.Text.Insert(document.CursorPosition, Clipboard.GetText()); } public bool CanExecute(object parameter) { return Clipboard.ContainsText(); } public event EventHandler CanExecuteChanged; }

Пример 3 Команды можно привязывать к определенным элементам управления на форме. Например XAML для кнопки, выполняющей команду сохранения, будет таким: Paste Отметим, что PasteCommand это поле объекта, который записан в свойство DataContext объемлющего для кнопки элемента.

Пример 4 Рассмотрим один способ создания команд без порождения наследников класса Command для каждой отдельной операции. Зачастую операции реализуются не в объекте команды, а в классе-получателе, которому команда лишь делегирует выполнение. Данный способ предусматривает создание специальной шаблонной команды, которая инициализируется объектом-получателем, указателем на функцию-член, которая реализует ту или иную операцию, и аргументами операции.

Пример 4 Рассмотрим два простых класса. Один из классов делит разные числа на заданное значение разными способами: class Divisor { int divisor; public: Divisor(int div): divisor(div) {} int divide(int input) { return input / divisor; } int modulus(int input){ return input % divisor; } };

Пример 4 Другой класс имеет некоторый набор методов для манипуляций над строками: class StringBuilder { string str; public: StringBuilder(string s): str(s) {} string prepend(string in) { return in + str; } string append(string in) { return str + in; } };

Пример 4 Класс шаблонной команды будет иметь следующий вид: template class Command { public: typedef TArgument (TClass::*Action)(TArgument); private: TClass* object; Action method; TArgument argument; public: Command(TClass* o, Action m, TArgument a) : object(o), method(m), argument(a) {} TArgument execute() { return (object->*method)(argument); } };

Пример 4 Рассмотрим теперь пример использования данного класса: vector > divisorCommands; divisorCommands.push_back( &Divisor::divide,16));Command (&Divisor(3), divisorCommands.push_back( &Divisor::modulus, 16)); Command (&Divisor(3), divisorCommands.push_back( &Divisor::divide,16)); Command (&Divisor(4), divisorCommands.push_back( Command (&Divisor(4),&Divisor::modulus, 16)); for (int i = 0; i < 4; i++) cout

Пример 4 Для класса StringBuilder использование шаблонной команды будет выглядеть так: StringBuilder obj("abc"); vector > commands; commands.push_back( Command (&obj, &StringBuilder::prepend, commands.push_back( Command (&obj, &StringBuilder::prepend, "123" )); "xyz" )); commands.push_back( Command (&obj, &StringBuilder::append, "123" )); commands.push_back( Command (&obj, &StringBuilder::append, "xyz" )); for (int i = 0; i < 4; i++) cout

Паттерн Компоновщик (Composite)

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

Диаграмма классов

Участники Component компонент. Объявляет интерфейс для компонуемых объектов. Предоставляет подходящую реализацию операций по умолчанию, общую для всех классов. Leaf лист. Объект того же типа что и Composite, но без реализации контейнерных функций. Представляет листовые узлы композиции и не имеет дочерних элементов. Composite составной объект. Определяет поведение контейнерных объектов, у которых есть потомки. Хранит иерархию компонентов-потомков. Реализует относящиеся к управлению потомками (контейнерные) операции в интерфейсе класса Component. Client клиент. Манипулирует объектами композиции, используя интерфейс Component.

Плюсы Поддерживает иерархии классов, состоящие из примитивных и составных объектов. Из примитивных объектов можно составлять более сложные, которые, в свою очередь, участвуют в более сложных композициях и так далее. Любой клиент, ожидающий примитивного объекта, сможет работать и с составным. Упрощает архитектуру клиента. Клиенты могут единообразно работать и с индивидуальными объектами, и с составными структурами. Облегчает добавление новых видов компонентов. Новые подклассы классов Composite или Leaf будут автоматически работать с уже существующими структурами и клиентским кодом. Изменять клиента при добавлении новых компонентов не нужно. Способствует использованию рекурсии.

Пример 1 Рассмотрим в качестве первого примера набор классов, представляющих собой модель файловой системы. Базовой сущностью является класс IFileSystemEntry: public interface IFileSystemEntry { string Name { get; set; } IEnumerable SubItems { get; } void AddSubItem(IFileSystemEntry subItem); void RemoveSubItem(IFileSystemEntry subItem); }

Пример 1 Листовым компонентом будет класс, который представляет файл. В языке C# конкретные классы должны реализовать все абстрактные методы своих базовых классов, поэтому в методах, которые не уместны для листовых элементов, следует сгенерировать исключения: public class File : IFileSystemEntry { public File(string fileName) { Name = fileName; } public string Name { get; set; } public IEnumerable SubItems { get { yield break; } } public void AddSubItem(IFileSystemEntry subItem) { throw new NotSupportedException(); } public void RemoveSubItem(IFileSystemEntry subItem) { throw new NotSupportedException(); }}}}

Пример 1 Класс, представляющий директорию, является составным элементом и будет иметь следующий вид: public class Directory : IFileSystemEntry { private readonly List subItems = new List (); public Directory(string directoryName) { Name = directoryName; } public string Name { get; set; } public IEnumerable SubItems { get { return subItems; } } public void AddSubItem(IFileSystemEntry subItem) { subItems.Add(subItem); } public void RemoveSubItem(IFileSystemEntry subItem) { subItems.Remove(subItem); }}}}

Пример 1 Примером использования может являться следующая рекурсивная функция, которая выводит на консоль структуру файловой системы, начиная с определнного места: public static class FileSystemUtils { private static void InternalShowFileSystemStructure( IFileSystemEntry root, int level) { Console.WriteLine(string.Empty.PadRight(level) + root.Name); foreach (IFileSystemEntry subItem in root.SubItems) InternalShowFileSystemStructure(subItem, level + 1); } public static void ShowFileSystemStructure(IFileSystemEntry root) { InternalShowFileSystemStructure(root, 0); }}}}

Пример 1 Результат работы следующего фрагмента кода будет таким: IFileSystemEntry root = new Directory("C:"); IFileSystemEntry windows = new Directory("Windows"); windows.AddSubItem(new Directory("system32")); windows.AddSubItem(new File("minesweeper.exe")); root.AddSubItem(windows); IFileSystemEntry temp = new Directory("Temp"); root.AddSubItem(temp); FileSystemUtils.ShowFileSystemStructure(root); C: Windows system32 minesweeper.exe Temp

Пример 2 Рассмотрим библиотеку графических примитивов, в которой допускается группировка графических элементов в произвольные древовидные структуры. Любой графический примитив представляется следующим интерфейсом: public interface IGraphic { Point Position { get; set; } void AddChild(IGraphic child); void RemoveChild(IGraphic child); IEnumerable Children { get; } }

Пример 2 Рассмотрим реализацию класса эллипса: public class Ellipse : IGraphic { public Ellipse(int semimajorAxis, int semiminorAxis) { SemimajorAxis = semimajorAxis; SemiminorAxis = semiminorAxis; } public Point Position { get; set; } public int SemimajorAxis { get; set; } public int SemiminorAxis { get; set; } public void AddChild(IGraphic child) { throw new NotSupportedException(); } public void RemoveChild(IGraphic child) { throw new NotSupportedException(); } public IEnumerable Children { get { yield break; } }}}}

Пример 2 Реализация класса прямоугольника будет следующей: public class Rectangle : IGraphic { public Rectangle(int width, int height) { Width = width; Height = height; } public int Width { get; set; } public int Height { get; set; } public Point Position { get; set; } public void AddChild(IGraphic child) { throw new NotSupportedException(); } public void RemoveChild(IGraphic child) { throw new NotSupportedException(); } public IEnumerable Children { get { yield break; } }}}}

Пример 2 Рассмотрим теперь класс составного элемента: public class CompositeGraphic : IGraphic { private readonly List children = new List (); public Point Position { get; set; } public void AddChild(IGraphic child) { children.Add(child); } public void RemoveChild(IGraphic child) { children.Remove(child); } public IEnumerable Children { get { return children; } }}}}

Пример 2 Предположим, что набор классов, представляющих графические примитивы почти или совсем не меняется. В таком случае для определения операций над элементами уместно применить паттерн Посетитель: public interface IGraphic { //... void Accept(GraphicVisitor visitor); } public abstract class GraphicVisitor { public abstract void Visit(Ellipse ellipse); public abstract void Visit(Rectangle rectangle); }

Пример 2 Реализация метода Accept в классах прямоугольника и эллипса будет следующей: public class Rectangle : IGraphic { public void Accept(GraphicVisitor visitor) { visitor.Visit(this); }}}} public class Ellipse : IGraphic { public void Accept(GraphicVisitor visitor) { visitor.Visit(this); }}}}

Пример 2 Класс составного элемента может реализовывать метод Accept следующим образом: public class CompositeGraphic : IGraphic { public void Accept(GraphicVisitor visitor) { foreach (IGraphic child in Children) child.Accept(visitor); }}}}

Пример 2 Операция рисования фигур может быть выделена в отдельный класс таким образом: public class Drawer : GraphicVisitor { public override void Visit(Ellipse ellipse) { // Нарисовать эллипс } public override void Visit(Rectangle rectangle) { // Нарисовать прямоугольник }}}}

Пример 2 Для определения внешней операции, которая выполняет сдвиг, потребуется добавить в класс GraphicVisitor метод посещения составного элемента: public abstract class GraphicVisitor { //... public abstract void Visit(CompositeGraphic composite); } public class CompositeGraphic : IGraphic { public void Accept(GraphicVisitor visitor) { visitor.Visit(this); }}}}

Пример 2 Цикл по дочерним элементам составного объекта следует разместить в конкретном классе посетителя: public class ChangePositionVisitor : GraphicVisitor { private Size offset; public ChangePositionVisitor(Size offset) { this.offset = offset; } public override void Visit(Ellipse ellipse) { ellipse.Position += offset; } public override void Visit(Rectangle rectangle) { rectangle.Position += offset; } public override void Visit(CompositeGraphic composite) { composite.Position += offset; foreach (IGraphic child in composite.Children) child.Accept(this); }}}}

Паттерн Строитель (Builder)

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

Диаграмма классов

Участники Builder строитель, абстрактный класс. Содержит перечень всех возможных абстрактных методов для создания и стыковки частей различных объектов (продуктов). ConcreteBuilder конкретный строитель, занимающийся созданием какого-либо одного объекта (продукта). Конструирует и связывает вместе части продукта посредством реализации интерфейса Builder. Director распорядитель. Конструирует продукт, пользуясь общим интерфейсом Builder-а. Product конечный продукт, построенный с помощью конкретного Builder-а.

Плюсы Позволяет задавать разное внутреннее представление продукта. Объект Director оперирует лишь абстрактным интерфейсом класса Builder. За этим абстрактным интерфейсом может быть скрыто какое угодно внутреннее представление объекта. Как следствие, для изменения этого внутреннего представления, нужно лишь определить новый вид строителя. Дает полный, более тонкий контроль над процессом конструирования. Строитель создат объект шаг за шагом под управлением распорядителя. И лишь когда продукт завершен, распорядитель забирает его у строителя. Это позволяет обеспечить более тонкий контроль над процессом конструирования, а, значит, и над внутренней структурой готового продукта.

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

Пример 1 В качестве примера рассмотрим процесс генерации электронных книг. Книга обычно имеет определенную структуру: она состоит из разделов с заголовком, внутри которых находятся параграфы, а также другие элементы. Кроме того, элетронные книги выпускается в разных форматах, самые популярные из них: HTML, FB2, PDF, DjVu.

Пример 1 Интерфейс, отвечающий за построение книги, может иметь следующий вид: public interface IBookBuilder { void BeginSection(string title); void EndSection(); void AddParagraph(string text); void AddDelimiter(); string GetResult(); }

Пример 1 Класс, который реализует данный интерфейс и генерирует книги в формате HTML, имеет следующий вид: public class HTMLBookBuilder: IBookBuilder { private string html = ""; private int sectionLevel = 1; public void BeginSection(string title) { html += string.Format(" {1} ", sectionLevel, title); sectionLevel++; } public void EndSection() { sectionLevel--; } public void AddParagraph(string text) { html += string.Format(" {0} ", text); } public void AddDelimiter() { html += " "; } public string GetResult() { return html; } }

Пример 1 Класс, отвечающий за постоение книг в формате FB2, будет следующим: public class FB2BookBuilder: IBookBuilder { private string fb2Xml = ""; public void BeginSection(string title) { fb2Xml += string.Format(" {0} ", title); } public void EndSection() { fb2Xml += " "; } public void AddParagraph(string text) { fb2Xml += string.Format(" {0} ", text); } public void AddDelimiter() { fb2Xml += " * * * "; } public string GetResult() { return fb2Xml; } }

Пример 1 Примером использования перечисленных выше классов может являться класс составителя книг такого вида: public class BookComposer { public void ComposeBook(IBookBuilder bookBuilder) { bookBuilder.BeginSection("Ромео и Джульетта"); bookBuilder.BeginSection("Пролог"); bookBuilder.AddParagraph("Две равно уважаемых семьи"); //... } public void CreateFB2Book (string fileName) { IBookBuilder bookBuilder = new FB2BookBuilder(); ComposeBook(bookBuilder); File.WriteAllText(fileName, bookBuilder.GetResult()); } public void CreateHTMLBook (string fileName) { IBookBuilder bookBuilder = new HTMLBookBuilder(); ComposeBook(bookBuilder); File.WriteAllText(fileName, bookBuilder.GetResult()); }

Пример 2 Рассмотрим использование данного паттерна на примере построения SELECT запросов к базе данных. Обратимся к примеру клиентского кода, который строит простой SELECT запрос: private static void BuildQuery(SelectQueryBuilder queryBuilder) { queryBuilder.AddFieldName("employee.first_name", "employee_first_name"); queryBuilder.AddFieldName("employee.last_name", "employee_first_name"); queryBuilder.AddFieldName("country.id", "country_id"); queryBuilder.AddFieldName("country.name", "country_name"); queryBuilder.AddSourceTable("employee"); queryBuilder.AddJoin("country", "country.id", "employee.country_id", JoinKind.LeftOuter); }

Пример 2 Построитель запросов реализует следующий интерфейс: public interface ISelectQueryBuilder { void AddSourceTable(string tableName); void AddFieldName(string fieldName, string alias); void AddJoin(string tableName, string leftSide, string rightSide, JoinKind joinKind); void AddWhereCondition(string condition); string GetSql(); }

Пример 2 Рассмотрим фрагмент реализации данного класса, отвечающий за добавление полей: public class SelectQueryBuilder : ISelectQueryBuilder { private StringBuilder fields = new StringBuilder(); public void AddFieldName(string fieldName, string alias) { fields.AppendString( string.Format("{0} AS {1}", fieldName, alias), ", "); } public string GetSql() { StringBuilder result = new StringBuilder(); result.Append("SELECT"); result.AppendString(fields.ToString(), " "); result.AppendString("FROM", " "); //... return result.ToString(); }}}}

Пример 2 Следующий фрагмент реализации отвечает за добавление условий в секцию WHERE: public class SelectQueryBuilder : ISelectQueryBuilder { private StringBuilder whereCondition = new StringBuilder(); public void AddWhereCondition(string condition) { whereCondition.AppendString(condition, " AND "); } public string GetSql() { StringBuilder result = new StringBuilder(); //... if (whereCondition.Length > 0) { result.AppendString("WHERE", " "); result.AppendString(whereCondition.ToString(), " "); } return result.ToString(); }}}}

Пример 2 Реализация данного построителя запросов для СУБД Oracle версии 8 и ниже учитывает особенности сервера и реализует метод AddJoin следущим образом: public class Oracle8SelectQueryBuilder : SelectQueryBuilder { public override void AddJoin(string tableName, string leftSide, string rightSide, JoinKind joinKind) { AddSourceTable(tableName); if (joinKind == JoinKind.LeftOuter) AddWhereCondition(string.Format("{0} = {1} (+)", leftSide, rightSide)); else if (joinKind == JoinKind.RightOuter) AddWhereCondition(string.Format("{0} (+) = {1}", leftSide, rightSide)); else AddWhereCondition(string.Format("{0} = {1}", leftSide, rightSide)); }}}}

Пример 2 Вспомним пример клиентского кода, который конструирует запрос: private static void BuildQuery(SelectQueryBuilder queryBuilder) { queryBuilder.AddFieldName("employee.first_name", "employee_first_name"); queryBuilder.AddFieldName("employee.last_name", "employee_first_name"); queryBuilder.AddFieldName("country.id", "country_id"); queryBuilder.AddFieldName("country.name", "country_name"); queryBuilder.AddSourceTable("employee"); queryBuilder.AddJoin("country", "country.id", "employee.country_id", JoinKind.LeftOuter); }

Пример 2 Рассмотрим примеры использования этой функции с разными реализациями построителя запросов: SelectQueryBuilder queryBuilder = new SelectQueryBuilder(); BuildQuery(queryBuilder); Console.WriteLine(queryBuilder.GetSql()); SELECT employee.first_name AS employee_first_name, employee.last_name AS employee_first_name, country.id AS country_id, country.name AS country_name FROM employee LEFT OUTER JOIN country ON country.id = employee.country_id Результатом работы этого фрагмента кода будет следущий скрипт:

Пример 2 Данный фрагмент кода: SelectQueryBuilder queryBuilder = new Oracle8SelectQueryBuilder(); BuildQuery(queryBuilder); Console.WriteLine(queryBuilder.GetSql()); SELECT employee.first_name AS employee_first_name, employee.last_name AS employee_first_name, country.id AS country_id, country.name AS country_name FROM employee, country WHERE country.id = employee.country_id (+) напечатает скрипт следущего вида: