Шаблоны и архитектура программ 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Шаблоны и архитектура программ



А.А. Волосевич

ШАБЛОНЫ И АРХИТЕКТУРА ПРОГРАММ

 

Курс лекций

для студентов специальности I-31 03 04 Информатика

всех форм обучения

 

Минск 2010

СОДЕРЖАНИЕ

 

3. ШАБЛОНЫ И АРХИТЕКТУРА ПРОГРАММ

3.1. МОДУЛЬНОЕ ТЕСТИРОВАНИЕ

3.2. ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

3.3. СТРУКТУРНЫЕ ШАБЛОНЫ: Декоратор, Заместитель, мост

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

Заместитель (Proxy)

Мост (Bridge)

3.4. СТРУКТУРНЫЕ ШАБЛОНЫ: КОМПОНОВЩИК И ПРИСПОСОБЛЕНЕЦ

Компоновщик (Composite)

Приспособленец (Flyweight)

3.5. СТРУКТУРНЫЕ ШАБЛОНЫ: АДАПТЕР И ФАСАД

Адаптер (Adapter)

Фасад (Façade)

3.6. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: ПРОТОТИП, ФАБРИЧНЫЙ МЕТОД, ОДИНОЧКА

Прототип (Prototype)

Фабричный метод (Factory method) #

Одиночка (Singleton)

3.7. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: АБСТРАКТНАЯ ФАБРИКА И СТРОИТЕЛЬ

Абстрактная фабрика (Abstract factory)

Строитель (Builder)

3.8. ШАБЛОНЫ ПОВЕДЕНИЯ: СТРАТЕГИЯ, СОСТОЯНИЕ, ШАБЛОННЫЙ МЕТОД

Стратегия (Strategy)

Состояние (State)

Шаблонный метод (Template method)

3.9. ШАБЛОНЫ ПОВЕДЕНИЯ: ЦЕПОЧКА ОБЯЗАННОСТЕЙ И КОМАНДА

Цепочка обязанностей (Chain of responsibility)

Команда (Command)

3.10. ШАБЛОНЫ ПОВЕДЕНИЯ: ИТЕРАТОР, ПОСРЕДНИК, НАБЛЮДАТЕЛЬ

Итератор (Iterator)

Посредник (Mediator)

Наблюдатель (Observer)

3.11. ШАБЛОНЫ ПОВЕДЕНИЯ: ПОСЕТИТЕЛЬ, ИНТЕРПРЕТАТОР, ХРАНИТЕЛЬ

Посетитель (Visitor)

Интерпретатор (Interpreter)

Хранитель (Memento)

3.12. НЕКОТОРЫЕ НЕКЛАССИЧЕСКИЕ ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Неизменный объект (Immutable object)

Пул объектов (Object pool)

Отложенная инициализация (Lazy initialization)

Нулевой объект (Null object)

3.13. АНТИПАТТЕРНЫ

3.14. АРХИТЕКТУРА ПРОГРаммного Обеспечения

«Клиент-сервер»

Архитектура, основанная на использовании компонентов

Многоуровневая архитектура

Шина сообщений

Выделенное представление

N-звеньевая архитектура

Объектно-ориентированная архитектура

Архитектура, ориентированная на сервисы

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

Шаблон Декоратор даёт способ для динамического добавления к объекту нового состояния и поведения. При этом исходный объект не знает о том, что он «декорируется». Ключевым моментом реализации шаблона является то, что класс-декоратор одновременно наследуется от декорируемого класса и агрегирует объект этого класса.

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

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

 

Рис. 3. UML-диаграмма шаблона Декоратор.

При практической реализации шаблона декорируемый объект обычно передается как параметр конструктора декоратора и сохраняется во внутреннем поле. Методы декоратора могут использовать сохраненный объект при своей работе.

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

using System.Drawing;

 

public class Photo

{

public Image Image { get; set; }

public string Caption { get; set; }

 

public Photo()

{

Image = new Bitmap(100, 100);

}

 

public virtual void Draw(Graphics canvas)

{

canvas.DrawImage(Image, 0, 0);

}

}

 

public class TaggedPhoto: Photo

{

private readonly Photo photo;

private readonly string tag;

 

public TaggedPhoto(Photo source, string tag)

{

photo = source;

this.tag = tag;

}

 

public override void Draw(Graphics canvas)

{

photo.Draw(canvas);

canvas.DrawString(tag, new Font("Arial", 16),

new SolidBrush(Color.Black),

new PointF(20, 20));

}

}

 

public class DecoratorExample

{

private static void Main()

{

var photo = new Photo();

var taggedPhoto = new TaggedPhoto(photo, "the first tag");

var doubleTagged = new TaggedPhoto(taggedPhoto,

"the second tag");

}

}

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

Заместитель (Proxy)

Шаблон проектирования Заместитель применяется для контроля создания и доступа к объекту. Заместитель – это обычно небольшой открытый объект, «за спиной» которого находится сложный объект, выполняющий реальную работу и создающийся только при наступлении определённых условий.

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

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

 

Рис. 4. UML-диаграмма шаблона Заместитель.

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

public interface ISubject

{

string Request();

}

 

internal class Subject

{

public string Request()

{

return "Subject Request";

}

}

 

// Виртуальный заместитель - создает реальный объект

// только при первом вызове его метода

public class VirtualProxy: ISubject

{

private Subject subject;

 

public string Request()

{

if (subject == null)

subject = new Subject();

return "Proxy: Call to " + subject.Request();

}

}

 

// Аутентификационный заместитель - для доступа нужен пароль

public class ProtectionProxy: ISubject

{

private Subject subject;

private const string Password = "Abracadabra";

 

public string Authenticate(string supplied)

{

if (supplied!= Password)

return "Protection Proxy: No access";

subject = new Subject();

return "Protection Proxy: Authenticated";

}

 

public string Request()

{

if (subject == null)

{

return "Protection Proxy: Authenticate first";

}

return "Protection Proxy: Call to " + subject.Request();

}

}

Мост (Bridge)

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

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

Рис. 5 демонстрирует упрощенную диаграмму дизайна шаблона Мост. На диаграмме показан только один абстрактный тип. Этот тип агрегирует некий конкретный объект-мост, которому делегируется выполнение требуемых операций.

 

Рис. 5. UML-диаграмма шаблона Мост.

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

public class Sensor

{

private readonly Bridge bridge;

 

public Sensor(Bridge bridge)

{

this.bridge = bridge;

}

 

public double GetValue()

{

return bridge.GetSensorValue();

}

}

 

public interface Bridge

{

double GetSensorValue();

}

 

public class ImplementationA: Bridge

{

public double GetSensorValue()

{

System.Console.WriteLine("Implementation A");

return -1;

}

}

 

public class ImplementationB: Bridge

{

public double GetSensorValue()

{

System.Console.WriteLine("Implementation B");

return 0;

}

}

 

public class BridgeExample

{

private static void Main()

{

var sensor = new Sensor(new ImplementationA());

sensor.GetValue();

}

}

3.4. СТРУКТУРНЫЕ ШАБЛОНЫ: КОМПОНОВЩИК И ПРИСПОСОБЛЕНЕЦ

Компоновщик (Composite)

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

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

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

 

Рис. 6. Дизайн шаблона Компоновщик.

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

using System;

using System.Collections.Generic;

 

public interface IComponent<T>

{

T Item { get; set; }

void Add(IComponent<T> c);

IComponent<T> Remove(T s);

IComponent<T> Find(T s);

}

 

// Отдельный компонент

public class Component<T>: IComponent<T>

{

public T Item { get; set; }

 

public Component(T item)

{

Item = item;

}

 

public void Add(IComponent<T> c)

{

Console.WriteLine("Cannot add to an item");

}

 

public IComponent<T> Remove(T s)

{

Console.WriteLine("Cannot remove directly");

return this;

}

 

public IComponent<T> Find(T s)

{

return s.Equals(Item)? this: null;

}

}

 

// Компоновщик

public class Composite<T>: IComponent<T>

{

private readonly List<IComponent<T>> list;

 

public T Item { get; set; }

 

public Composite(T item)

{

Item = item;

list = new List<IComponent<T>>();

}

 

public void Add(IComponent<T> c)

{

list.Add(c);

}

 

public IComponent<T> Remove(T s)

{

var p = Find(s);

if (p!= null)

{

list.Remove(p);

}

return this;

}

 

public IComponent<T> Find(T s)

{

if (Item.Equals(s))

{

return this;

}

IComponent<T> found = null;

foreach (var c in list)

{

found = c.Find(s);

if (found!= null)

{

break;

}

}

return found;

}

}

Приспособленец (Flyweight)

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

Рассмотрим диаграмму шаблона Приспособленец (рис. 7).

 

Рис. 7. Диаграмма шаблона Приспособленец.

Суть шаблона в том, чтобы разделить состояние некоторого объекта на состояния трех типов. Внутреннее состояние (intrinsic state) принадлежит самому объекту. Тип Flyweight реализует интерфейс IFlyweight, который определяет операции, в которых заинтересована остальная часть системы. Клиент владеет общим (неразделяемым) состоянием (unshared state), а также коллекцией приспособленцев, которых производит класс-фабрика (FlyweightFactory). Наконец, внешнее состояние (extrinsic state) не появляется в системе как таковое. Если оно понадобится, то будет вычислено уже во время выполнения программы для каждого внутреннего состояния.

Отметим некоторые нюансы реализации шаблона Приспособленец. Тип Flyweight может быть реализован в виде структуры – он небольшой и ни от чего не наследует. Фабрика приспособленцев создает объекты в соответствии со специальными требованиями: нужно проверить, существует ли объект, и если не существует, то создать его и добавить во внутреннюю коллекцию-словарь.

using System;

using System.Collections.Generic;

 

public class CharacterFactory

{

private readonly Dictionary<char, Character> chars =

new Dictionary<char, Character>();

 

public Character GetCharacter(char key)

{

if (!chars.ContainsKey(key))

{

switch (key)

{

case 'A':

chars.Add(key, new CharacterA());

break;

case 'B':

chars.Add(key, new CharacterB());

break;

}

}

return chars[key];

}

}

 

public abstract class Character

{

public char Symbol { get; set; }

public int Width { get; set; }

public int Height { get; set; }

public int PointSize { get; set; }

public abstract void Display(int pointSize);

}

 

public class CharacterA: Character

{

public CharacterA()

{

Symbol = 'A';

Height = 100;

Width = 120;

}

 

public override void Display(int pointSize)

{

PointSize = pointSize;

Console.WriteLine("{0} (pointsize {1})", Symbol, PointSize);

}

}

 

public class CharacterB: Character

{

public CharacterB()

{

Symbol = 'B';

Height = 100;

Width = 120;

}

 

public override void Display(int pointSize)

{

PointSize = pointSize;

Console.WriteLine("{0} (pointsize {1})", Symbol, PointSize);

}

}

 

public class FlyweightExample

{

private static void Main()

{

// создаём "документ" с текстом

var document = "AABBAB";

var chars = document.ToCharArray();

var f = new CharacterFactory();

// pointSize - это внешнее состояние

var pointSize = 10;

// используем для каждого символа объект-приспособленец

foreach (var c in chars)

{

pointSize++;

var character = f.GetCharacter(c);

character.Display(pointSize);

}

}

}

3.5. СТРУКТУРНЫЕ ШАБЛОНЫ: АДАПТЕР И ФАСАД

Адаптер (Adapter)

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

Дизайн шаблона Адаптер показан на рис. 8.

 

Рис. 8. Дизайн шаблона Адаптер.

В адаптере четко видно преимущество программирования согласно интерфейсам. Client работает в соответствии с требованиями своей предметной области, эти требования отражены в целевом интерфейсе ITarget (интерфейс, в котором заинтересован клиент). Адаптируемый класс Adaptee обладает требуемой функциональностью, но неподходящим интерфейсом. Adapter реализует интерфейс ITarget и перенаправляет вызовы от Client к Adaptee, изменяя при необходимости параметры и возвращаемые значения.

Существует несколько разновидностей адаптеров. На рис. 8 показан адаптер класса, поскольку он одновременно реализует интерфейс и наследует от адаптируемого класса Adaptee. Альтернативой наследованию может стать агрегация объекта типа Adaptee. В этом случае получится адаптер объекта. Разница в этих двух способах в том, что при наследовании легче переопределить поведение Adaptee, а при агрегации – добавить к Adaptee поведение.

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

using System;

 

public interface ITarget

{

string Request(int i);

}

 

public class Adaptee

{

public double SpecificRequest(double a, double b)

{

return a / b;

}

}

 

public class Adapter: Adaptee, ITarget

{

public string Request(int i)

{

return "Rough: " + (int)Math.Round(SpecificRequest(i, 3));

}

}

 

public class Client

{

private static void Main()

{

var adaptee = new Adaptee();

Console.Write("Before the new standard: ");

Console.WriteLine(adaptee.SpecificRequest(5, 3));

ITarget adapter = new Adapter();

Console.Write("Moving to the new standard: ");

Console.WriteLine(adapter.Request(5));

}

}

Особенность адаптеров в том, что они могут добавлять дополнительное поведение к тому поведению, что специфицируется в ITarget и в Adaptee. Другими словами, адаптеры могут быть прозрачными для клиента и непрозрачными. В примере кода показан последний случай, где Adapter добавляет "Rough:". Эта добавка показывает, что вызов Request() был адаптирован (изменен) перед тем, как был вызван метод SpecificRequest().

Фасад (Façade)

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

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

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

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

 

Рис. 9. Дизайн шаблона проектирования Фасад.

3.6. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: ПРОТОТИП, ФАБРИЧНЫЙ МЕТОД, ОДИНОЧКА

Прототип (Prototype)

Шаблон Прототип позволяет создавать специальные объекты, ничего не знаю об их классе или деталях создания. Механизм можно описать так: клиенту, инициирующему создание объектов, предоставляются объекты-прототипы. Затем целевые объекты создаются путем клонирования этих объектов-прототипов.

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

 

Рис. 10. Дизайн шаблона Прототип.

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

using System;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

 

[Serializable]

// Тип T должен быть сериализуемым!

public abstract class Prototype<T>

{

// Поверхностное копирование

public T Clone()

{

return (T)MemberwiseClone();

}

 

// Глубокое копирование

public T DeepCopy()

{

var stream = new MemoryStream();

var formatter = new BinaryFormatter();

formatter.Serialize(stream, this);

stream.Seek(0, SeekOrigin.Begin);

var copy = (T)formatter.Deserialize(stream);

stream.Close();

return copy;

}

}

Фабричный метод (Factory method)

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

Для иллюстрации шаблона Фабричный метод представим магазин, торгующий определенным видом фруктов. Магазин (выступающий в роли клиента) работает с поставщиком (фабричный метод), который в зависимости от времени года импортирует фрукты (объекты) из разных стран (разные классы объектов). Как видим, детали операций импорта от клиента скрыты и независимы.

 

Рис. 11. UML-диаграмма шаблона Фабричный метод.

Далее приведён код, использующий шаблон Фабричный метод и соответствующий описанному выше гипотетическому примеру.

using System;

 

public interface IProduct

{

string ShipFrom();

}

 

public class ProductFromAfrica: IProduct

{

public String ShipFrom()

{

return "from South Africa";

}

}

 

public class ProductFromSpain: IProduct

{

public String ShipFrom()

{

return "from Spain";

}

}

 

public class DefaultProduct: IProduct

{

public String ShipFrom()

{

return "not available";

}

}

 

public class Creator

{

public IProduct FactoryMethod(int month)

{

if (month >= 4 && month <= 11)

{

return new ProductFromAfrica();

}

if (month == 12 || month == 2 || month == 1)

{

return new ProductFromSpain();

}

return new DefaultProduct();

}

}

 

public class FactoryMethodExample

{

private static void Main()

{

var c = new Creator();

IProduct product;

for (var i = 1; i <= 12; i++)

{

product = c.FactoryMethod(i);

Console.WriteLine("Avocados " + product.ShipFrom());

}

}

}

Одиночка (Singleton)

Шаблон Одиночка гарантирует создание единственного экземпляра объекта некоторого класса.

Необходимость в единичных объектах часто возникает при программировании физических устройств. Если в системе установлен единственный принтер, то нужно быть уверенным, что в программе имеется единственный объект, описывающий этот принтер и предоставляющий доступ к его настройкам. Иначе возможна ситуация рассогласования – различные (возможно, противоречивые) настройки будут выполняться в разных частях программы над одним физическим объектом.

Шаблон Одиночка добавляет функциональность путём модификации существующего класса. Модификация требует следующих шагов.

● Конструктор класса делается закрытым (private).

● Добавляется закрытое статическое поле только для чтения, которое инстанциируется, используя закрытый экземплярный конструктор класса.

● Для доступа к закрытому статическому полю добавляется открытое статическое свойство.

Рис. 12 иллюстрирует указанные шаги.

 

Рис. 12. UML-диаграмма шаблона Одиночка.

Код, который соответствует описанным шагам реализации шаблона, представлен ниже.

public sealed class Singleton

{

// Закрытый конструктор

private Singleton() { }

 

// Закрытое поле, хранит экземпляр класса

private static readonly Singleton uniqueInstance =

new Singleton();

 

// Открытое свойство для доступа к экземпляру

public static Singleton Instance

{

get { return uniqueInstance; }

}

}

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

// Один из вариантов реализации шаблона Singleton

public sealed class Singleton

{

private Singleton() { }

 

private static Singleton uniqueInstance;

 

public static Singleton Instance

{

get

{

if (uniqueInstance == null)

{

uniqueInstance = new Singleton();

}

return uniqueInstance;

}

}

}

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

3.7. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: АБСТРАКТНАЯ ФАБРИКА И СТРОИТЕЛЬ

Строитель (Builder)

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

Рассмотрим дизайн шаблона Строитель. В его основе лежит компонент Director (режиссер), который вызывает объекты-строители. Количество объектов-строителей произвольно, однако все они реализуют интерфейс IBuilder. Строители поставляют элементы для создания финального объекта Product. Как только объект Product будет создан, компонент Director предоставляет его клиенту.

 

Рис. 14. Дизайн шаблона Строитель.

Пример кода с реализацией шаблона Строитель основан на примере для шаблона Абстрактная фабрика.

using System;

using System.Threading;

 

public interface IBuilder

{

IBag CreateBag();

}

 

public class Builder<TBrand>: IBuilder where TBrand: IBrand, new()

{

private readonly TBrand myBrand = new TBrand();

 

public IBag CreateBag()

{

return myBrand.CreateBag();

}

}

 

public interface IBag

{

string Properties { get; set; }

}

 

public class Bag: IBag

{

public string Properties { get; set; }

}

 

public interface IBrand

{

IBag CreateBag();

}

 

public class Gucci: IBrand

{

public IBag CreateBag()

{

var b = new Bag();

BuilderExample.DoWork("Cut Leather", 250);

BuilderExample.DoWork("Sew leather", 1000);

b.Properties += "Leather";

BuilderExample.DoWork("Create Lining", 500);

BuilderExample.DoWork("Attach Lining", 1000);

b.Properties += " lined";

BuilderExample.DoWork("Add Label", 250);

b.Properties += " with label";

return b;

}

}

 

public class Poochy: IBrand

{

public IBag CreateBag()

{

var b = new Bag();

BuilderExample.DoWork("Hire cheap labour", 200);

BuilderExample.DoWork("Cut Plastic", 125);

BuilderExample.DoWork("Sew Plastic", 500);

b.Properties += "Plastic";

BuilderExample.DoWork("Add Label", 100);

b.Properties += " with label";

return b;

}

}

 

public class Client<TBrand> where TBrand: IBrand, new()

{

public void ClientMain()

{

IBuilder factory = new Builder<TBrand>();

var date = DateTime.Now;

Console.WriteLine("I want to buy a bag!");

var bag = factory.CreateBag();

Console.WriteLine("I got my Bag which took " +

DateTime.Now.Subtract(date).TotalSeconds * 5 + " days");

Console.WriteLine("with the following properties " +

bag.Properties);

}

}

 

public static class BuilderExample

{

private static void Main()

{

new Client<Poochy>().ClientMain();

new Client<Gucci>().ClientMain();

}

 

public static void DoWork(string workitem, int time)

{

Console.Write("{0}: 0%", workitem);

Thread.Sleep(time);

Console.Write("....25%");

Thread.Sleep(time);

Console.Write("....50%");

Thread.Sleep(time);

Console.WriteLine("....100%");

}

}

3.8. ШАБЛОНЫ ПОВЕДЕНИЯ: СТРАТЕГИЯ, СОСТОЯНИЕ, ШАБЛОННЫЙ МЕТОД

Стратегия (Strategy)

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

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

 

Рис. 15. UML-диаграмма шаблона Стратегия.

Ниже приведён пример кода с реализацией шаблона Стратегия.

using System.Collections.Generic;

 

public interface IHolidaySet

{

List<string> GetHolidays();

}

 

public class USHolidaySet: IHolidaySet

{

public List<string> GetHolidays()

{

return new List<string> { "01.01.09", "25.05.09",

"04.07.09", "07.09.09",

"26.11.09", "25.12.09" };

}

}

 

public class RussianHolidaySet: IHolidaySet

{

public List<string> GetHolidays()

{

return new List<string> { "01.01.09", "07.01.09",

"23.02.09", "08.03.09",

"19.04.09", "12.06.09",

"04.11.09" };

}

}

 

public class Client

{

private readonly IHolidaySet holidaySetStrategy;

 

public Client(IHolidaySet strategy)

{

holidaySetStrategy = strategy;

}

 

public bool CheckForHoliday(string date)

{

return holidaySetStrategy.GetHolidays().Contains(date);

}

}

 

public static class StrategyExample

{

private static void Main()

{

var client = new Client(new USHolidaySet());

var result = client.CheckForHoliday("01.01.09");

}

}

Состояние (State)

Шаблон Состояние можно рассматривать как динамическую версию шаблона Стратегия. При использовании шаблона Состояние поведение объекта меняется в зависимости от текущего контекста.

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

Рассмотрим пример конечного автомата. Опишем поведение родителя, отправившего сына в школу. Сын приносит двойки и пятерки. Отец не хочет хвататься за ремень каждый раз, как только сын получит очередную двойку, и выбирает более тонкую тактику воспитания. Чтобы описать модель поведения отца, используем граф, в котором вершины соответствуют состояниям, а дуга, помеченная х/у, из состояния s в состояние q проводится тогда, когда автомат из состояния s под воздействием входного сигнала х переходит в состояние q с выходной реакцией у. Граф автомата, моделирующего поведение родителя, представлен на рис. 16.

 

Рис. 16. Автомат, описывающий поведение «умного» отца.

Этот автомат имеет четыре состояния s0, s1,s2,s3 и два входных сигнала – оценки, полученные сыном в школе: {2, 5}. Начиная с начального состояния s0 (оно помечено особо), автомат под воздействием входных сигналов переходит из одного состояния в другое и выдаёт выходные сигналы – реакции на входы. Выходы автомата будем интерпретировать как действия родителя так:

● y0 – «брать ремень»;

● y1 – «ругать»;

● y2 – «успокаивать»;

● y3 – «надеяться»;

● y4 – «радоваться»;

● y5 – «ликовать».

При наивной программной реализации конечного автомата порождаются наборы конструкций switch-case, которые, как правило, вложены в друг друга. Использование шаблона Состояние позволяет упростить код. Все состояния конечного автомата описываются отдельными классами, которые обладают набором виртуальных методов, соответствующих входным сигналам. Получение очередного входного сигнала означает вызов метода того объекта, экземпляр которого находится в поле state. При этом сам вызываемый метод может поместить в state другой объект-состояние (переключить состояние).

 

Рис. 17. Дизайн шаблона состояние.

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

public class SmartParent

{

internal AbstractState _state = State0.Instance;

 

public void HandleTwo()

{

_state.HandleTwo(this);

}

 

public void HandleFive()

{

_state.HandleFive(this);

}

}

 

public abstract class AbstractState

{

public abstract void HandleTwo(SmartParent parent);

public abstract void HandleFive(SmartParent parent);

}

 

public class State0: AbstractState

{

private static State0 _instance;

 

public static State0 Instance

{

get { return _instance?? (_instance = new State0()); }

}

 

public override void HandleTwo(SmartParent parent)

{

Console.WriteLine("Успокаивать");

parent._state = State1.Instance;

}

 

public override void HandleFive(SmartParent parent)

{

Console.WriteLine("Радоваться");

parent._state = State3.Instance;

}

}

 

public class State1: AbstractState

{

private static State1 _instance;

 

public static State1 Instance

{

get { return _instance?? (_instance = new State1()); }

}

 

public override void HandleTwo(SmartParent parent)

{

Console.WriteLine("Ругать");

parent._state = State2.Instance;

}

 

public override void HandleFive(SmartParent parent)

{

Console.WriteLine("Надеяться");

parent._state = State0.Instance;

}

}

 

public class State2: AbstractState

{

private static State2 _instance;

 

public static State2 Instance

{

get { return _instance?? (_instance = new State2()); }

}

 

public override void HandleTwo(SmartParent parent)

{

Console.WriteLine("Брать ремень");

parent._state = State2.Instance;

}

 

public override void HandleFive(SmartParent parent)

{

Console.WriteLine("Надеяться");

parent._state = State0.Instance;

}

}

 

public class State3: AbstractState

{

private static State3 _instance;

 

public static State3 Instance

{

get { return _instance?? (_instance = new State3()); }

}

 

public override void HandleTwo(SmartParent parent)

{

Console.WriteLine("Успокаивать");

parent._state = State1.Instance;

}

 

public override void HandleFive(SmartParent parent)

{

Console.WriteLine("Ликовать");

parent._state = State3.Instance;

}

}

Шаблонный метод (Template method)

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

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

using System;

 

public abstract class Sorter

{

private readonly int[] data;

 

protected Sorter(params int[] source)

{

data = new int[source.Length];

Array.Copy(source, data, source.Length);

}

 

protected abstract bool Compare(int x, int y);

 

public void Sort()

{

for (var i = 0; i < data.Length - 1; i++)

{

for (var j = i + 1; j < data.Length; j++)

{

if (Compare(data[j], data[i]))

{

var temp = data[i];

data[i] = data[j];

data[j] = temp;

}

}

}

}

 

public int[] GetData()

{

var result = new int[data.Length];

Array.Copy(data, result, data.Length);

return result;

}

}

 

public class GreaterFirstSorter: Sorter

{

public GreaterFirstSorter(params int[] source): base(source){ }

 

protected override bool Compare(int x, int y)

{

return x > y;

}

}

 

public class TemplateMethodExample

{

private static void Main()

{

var sorter = new GreaterFirstSorter(1, 3, -10, 0);

sorter.Sort();

var result = sorter.GetData();

}

}

3.9. ШАБЛОНЫ ПОВЕДЕНИЯ: ЦЕПОЧКА ОБЯЗАННОСТЕЙ И КОМАНДА

Итератор (Iterator)

Назначение шаблона Итератор – предоставить последовательный доступ к элементам коллекции без информации о её внутреннем устройстве. Дополнительно шаблон может реализовывать функционал по фильтр



Поделиться:


Последнее изменение этой страницы: 2016-09-13; просмотров: 126; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.144.189.177 (0.858 с.)