Паттерн "Абстрактная фабрика"

advertisement
Паттерн
Абстрактная Фабрика
(Abstract Factory)
Определение и мотивация
Определение
Абстрактная фабрика (Abstract factory) ― паттерн, порождающий объекты,
предоставляющий интерфейс для создания семейств логически связанных
или зависимых объектов без указания конкретных классов.
Мотивация
Система не должна зависеть от того, как создаются, компонуются и

представляются входящие объекты. Объекты, построенные определенным
образом, просто подаются на вход.
Объекты одного семейства должны использоваться вместе.

Конкретный вариант требуемого поведения системы дают не отдельные
объекты, а четко выраженное семейство связанных объектов.
Для функционирования системы необходимо одно из таких семейств

взаимосвязанных объектов.
На вход системы подается только целое семейство таких объектов,
система конфигурируется одним из семейств.
Требуется предоставить библиотеку объектов, раскрывая только их

интерфейсы, но не реализацию.
Плюсы




Скрываются реализации конкретных классов
Фабрика помогает контролировать классы объектов, создаваемых
приложением, и изолирует клиента от деталей реализации классов. Она
также инкапсулирует ответственность за создание классов и сам процесс
их создания. Клиенту известны лишь интерфейсы абстрактных классов,
через которые он только и может выполнять дальше свою бизнес логику с
созданным семейством объектов.
Свободная замена семейства продуктов
Класс конкретной фабрики появляется в приложении только один раз: при
инстанцировании. Это облегчает замену используемой приложением
конкретной фабрики. Приложение может изменить конфигурацию
продуктов, просто подставив новую конкретную фабрику.
Гарантия использования только одного вида продуктов
Если продукты некоторого семейства спроектированы для совместного
использования, то важно, чтобы приложение в каждый момент времени
работало только с продуктами единственного семейства. Класс
AbstractFactory позволяет легко соблюсти это ограничение.
Упрощается тестирование
При тестировании легко заменить целое семейство объектов другим
набором классов, который будет эмулировать поведение реальной
системы.
Диаграмма классов
Участники





AbstractFactory ― абстрактная фабрика
Объявляет общий интерфейс для операций создания абстрактных
объектов-продуктов. Далее в приложении вместо абстрактных будут
создаваться уже конкретные объекты какого-либо одного семейство.
ConcreteFactory[N] ― конкретная фабрика
Определяет, реализует операции, создающие все объекты одного
конкретного семейства.
AbstractProduct[A-Z] ― абстрактный продукт
Определяет общий интерфейс для типа объекта-продукта. A-Z в данном
случае ― множество таких продуктов, составляющих абстрактное
семейство.
ConcreteProduct[A-Z][N] ― конкретный продукт
Определяет конкретный объект-продукт типа [A-Z], создаваемый
соответствующей конкретной фабрикой [N].
Client ― клиент
Другой программный модуль, фрагмент кода, дающий команды на
получение конкретного семейства продуктов, пользуясь только известными
интерфейсами классов AbstractFactory и AbstractProduct.
Пример №1
Рассмотрим задачу производства автомобилей и
двигателей. Определим сущности автомобиля,
двигателя и фабрики, создающей данные сущности:
public interface ICarFactory
{
ICar CreateCar();
IEngine CreateEngine();
}
public interface ICar
{
string GetName();
}
public interface IEngine
{
string GetName();
}
Пример №1
Реализация сущности автомобиля марки «Форд» и
соответствующего двигателя:
public class Ford : ICar
{
public string GetName()
{
return "Ford";
}
}
public class FordEngine : IEngine
{
public string GetName()
{
return "Ford Engine 4.4";
}
}
Пример №1
Фабрика, отвечающая за создания автомобилей и
двигателей марки «Форд»:
public class FordFactory : ICarFactory
{
public ICar CreateCar()
{
return new Ford();
}
public IEngine CreateEngine()
{
return new FordEngine();
}
}
Пример №1
Реализация сущности автомобиля марки «Тойота» и
соответствующего двигателя:
public class Toyota : ICar
{
public string GetName()
{
return "Toyota";
}
}
public class ToyotaEngine : IEngine
{
public string GetName()
{
return "Toyota Engine 3.2";
}
}
Пример №1
Фабрика, отвечающая за создания автомобилей и
двигателей марки «Тойота»:
public class ToyotaFactory : ICarFactory
{
public ICar CreateCar()
{
return new Toyota();
}
public IEngine CreateEngine()
{
return new ToyotaEngine();
}
}
Пример №1
Рассмотрим пример использования фабрик:
public void Use(ICarFactory factory)
{
ICar myCar = factory.CreateCar();
IEngine myEngine = factory.CreateEngine();
Console.WriteLine(myCar.GetName());
Console.WriteLine(myEngine.GetName());
}
Use(new ToyotaFactory());
Use(new FordFactory());
Пример №2
Рассмотрим пример использования шаблона
Абстрактная фабрика для поддержки адресов и
номеров телефонов разных стран. В каждой стране
определѐн свой телефонный код и свой стандарт
записи адреса.
Интерфейс фабрики, создающий объекты адреса и
номера телефонов, выглядит следующим образом:
public interface IAddressFactory
{
Address CreateAddress();
PhoneNumber CreatePhoneNumber();
}
Пример №2
Ниже определѐн базовый класс адреса:
public abstract class Address {
public string Street
{ get; set; }
public string City
{ get; set; }
public string Region
{ get; set; }
public string PostalCode
{ get; set; }
public abstract string GetFullAddress();
public abstract string Country
{
get;
}
}
Пример №2
Базовый класс телефонного номера выглядит
следующим образом:
public abstract class PhoneNumber
{
public string Number
{ get; set; }
public abstract string CountryCode
{
get;
}
}
Пример №2
Конкретный класс продукта, который представляет
адреса США:
public class USAAddress : Address
{
public override string GetFullAddress()
{
return Street + Environment.NewLine +
City + ", " + Region + " " + PostalCode +
Environment.NewLine +
Country;
}
public override string Country
{
get { return "United States"; }
}
}
Пример №2
Класс представляющий телефонные номера в США:
public class USAPhoneNumber : PhoneNumber
{
public override string CountryCode
{
get { return "01"; }
}
}
Фабрика, которая инстанцирует экземпляры адреса и
телефонного номера для США:
public class USAAddressFactory : IAddressFactory {
public Address CreateAddress() {
return new USAAddress();
}
public PhoneNumber CreatePhoneNumber() {
return new USAPhoneNumber();
}
}
Пример №2
Конкретный класс продукта, который представляет
адреса во Франции:
public class FrenchAddress : Address
{
public override string GetFullAddress()
{
return Street + Environment.NewLine +
PostalCode + " " + City + Environment.NewLine +
Country;
}
public override string Country
{
get { return "France"; }
}
}
Пример №2
Класс представляющий телефонные номера во Франции:
public class FrenchPhoneNumber : PhoneNumber
{
public override string CountryCode
{
get { return "33"; }
}
}
Фабрика, которая инстанцирует экземпляры адреса и
телефонного номера для Франции:
public class FrenchAddressFactory : IAddressFactory {
public Address CreateAddress() {
return new FrenchAddress();
}
public PhoneNumber CreatePhoneNumber() {
return new FrenchPhoneNumber();
}
}
Пример №3
Предположим, что мы пишем игру, в которой игроку
предстоит встречаться с разными врагами: солдатами,
монстрами и супермонстрами. Все враги, которые
встретятся игроку, должны соответствовать уровню
сложности, выбранному в начале игры.
Фабрика для создания врагов может быть следующей:
class AbstractEnemyFactory
{
public:
virtual Soldier* MakeSoldier() = 0;
virtual Monster* MakeMonster() = 0;
virtual SuperMonster* MakeSuperMonster() = 0;
};
Пример №3
Фабрика, создающая врагов для низкого уровня
сложности:
class EasyLevelEnemyFactory : public AbstractEnemyFactory {
public:
Soldier* MakeSoldier() {
return new SillySoldier;
}
Monster* MakeMonster() {
return new SillyMonster;
}
SuperMonster* MakeSuperMonster() {
return new SillySuperMonster;
}
};
Пример №3
Фабрика, создающая врагов для высокого уровня
сложности:
class DieHardLevelEnemyFactory : public AbstractEnemyFactory {
public:
Soldier* MakeSoldier() {
return new BadSoldier;
}
Monster* MakeMonster() {
return new BadMonster;
}
SuperMonster* MakeSuperMonster(){
return new BadSuperMonster;
}
};
Пример №3
У такого подхода имеются следующие недостатки:
 Дублирование
При реализации фабрик, приходится прибегать к
многократному повторению одного и того же кода.

Невозможность обобщения
Невозможно написать обобщенный код следующего
вида:
template <class T>
T* MakeNamedEnemy(AbstractEnemyFactory& factory, const string& name)
{
T* result = factory.CreateT(); // Невозможно!
result->SetName(name);
return result;
}
Необходимо вызывать конкретные методы фабрики.
Пример №3
Избавиться от этих недостатков можно было бы, если
классу абстрактной фабрики передавать список
параметров в виде типов, которые она должна
создавать:
typedef AbstractFactory<Soldier, Monster, SuperMonster>
AbstractEnemyFactory; // Псевдокод
Тогда функции создания объектов могли бы
вызываться из обобщенного кода:
template <class T>
T* MakeNamedEnemy(AbstractEnemyFactory& factory, const string& name)
{
T* result = factory.Create<T>();
result->SetName(name);
return result;
}
Списки типов
Для реализации таких фабрик в библиотеке Loki
предусмотрено понятие списка типов.
Класс списка типов определяется следующим образом:
template <class T, class U>
struct Typelist {
typedef T Head;
typedef U Tail;
};
Для определения списка типов произвольной длины
используется рекурсия. Например, список типов из трѐх
элементов выглядит таким образом:
typedef Typelist<Class1, TypeList<Class2, Class3> > MyTypeList;
Списки типов
Кроме того библиотека содержит набор макросов для
упрощенного определения списка типов:
#define LOKI_TYPELIST_1(T1) Typelist<T1, NullType>
#define LOKI_TYPELIST_2(T1, T2) Typelist<T1, LOKI_TYPELIST_1(T2) >
#define LOKI_TYPELIST_3(T1, T2, T3) \
Typelist<T1, LOKI_TYPELIST_2(T2, T3) >
#define LOKI_TYPELIST_4(T1, T2, T3, T4) \
Typelist<T1, LOKI_TYPELIST_3(T2, T3, T4) >
Данные макросы используются таким образом:
typedef LOKI_TYPELIST_3(Class1, Class2, Class3) MyTypeList;
Пример №3
Библиотека Loki содержит механизм для создания
абстрактных фабрик.
Для определения абстрактной фабрики используется
класс AbstractFactory, который в качестве шаблонного
параметра принимает список типов, экземпляры
которых должна инстанцировать фабрика:
typedef AbstractFactory
<
LOKI_TYPELIST_3(Soldier, Monster, SuperMonster)
>
AbstractEnemyFactory;
Пример №3
Для определения конкретных фабрик необходимо
использовать класс ConcreteFactory:
typedef ConcreteFactory
<
// Абстрактная фабрика, подлежащая реализации
AbstractEnemyFactory,
// Стратегия создания объектов
OperatorNewFactoryUnit,
// Конкретные классы, создаваемые данной фабрикой
LOKI_TYPELIST_3(SillySoldier, SillyMonster, SillySuperMonster)
>
EasyLevelEnemyFactory;
Пример №3
В результате становится возможно использовать
фабрику в следующей обобщенной функции:
template<class T>
void CreateAndPrintName(EasyLevelEnemyFactory& factory)
{
T* enemy = factory.Create<T>();
std::cout << enemy->GetName() << std::endl;
delete enemy;
}
Код, использующий данную функцию, может быть таким:
EasyLevelEnemyFactory factory;
CreateAndPrintName<Soldier>(factory);
CreateAndPrintName<Monster>(factory);
CreateAndPrintName<SuperMonster>(factory);
Паттерн Мост
(Bridge)
Определение
Определение
Мост — структурный паттерн, отделяющий
абстракцию от реализации так, что и то и
другое можно изменять независимо.
Предпосылки использования
Пусть перед нами стоит задача разработки
набора графических примитивов.
Наша система должна поддерживать
разные графические библиотеки (OpenGL,
DirectX и т.д.).
Объявим сущность первого графического
примитива.
Предпосылки использования
Далее определим еѐ наследников и
перекроем методы Draw для каждого
варианта графической библиотеки.
Предпосылки использования
При добавлении нового графического примитива поступаем
аналогично.
В результате:
Число классов = число примитивов * число графических библиотек
Предпосылки использования
Паттерн мост призван отделить реализацию (графическую библиотеку)
от абстракции (графический примитив):
В результате:
Число классов = число примитивов + число графических библиотек
Мотивация





Требуется избежать привязки абстракции к
реализации.
Необходимо выбирать конкретную
реализацию во время выполнения
программы.
И абстракции, и реализации должны
расширяться новыми подклассами.
Изменения в реализации абстракции не
должны сказываться на клиентах.
Требуется разделить одну и ту же
реализацию между несколькими объектами, и
этот факт необходимо скрыть от клиента.
Диаграмма классов
Участники




Abstraction — абстракция.
Определяет интерфейс абстракции и агрегирует (хранит ссылку)
на объект типа Implement.
RefinedAbstraction — уточненная абстракция
Более специфичный подкласс основной абстракции, расширяет
интерфейс, определенный абстракцией Abstraction.
Implementor — исполнитель.
Определяет интерфейс для классов реализации. Он не обязан
точно соответствовать интерфейсу класса Abstraction, более того
оба интерфейса могут быть совершенно различны. Обычно
интерфейс класса Implementor предоставляет только примитивные
операции, а класс Abstraction определяет операции более высокого
уровня, базирующиеся на этих примитивах.
ConcreteImplementor — один из конкретных исполнителей.
Содержит конкретную реализацию интерфейса класса Implementor,
т.е. по-своему реализует примитивы, определенные в Implementorе.
Плюсы



Отделение реализации от интерфейса.
Реализацию абстракции можно конфигурировать во время
выполнения. Разделение классов Abstraction и Implementor
устраняет также зависимости от реализации, устанавливаемые на
этапе компиляции. Чтобы изменить класс реализации, теперь
вовсе не обязательно перекомпилировать класс Abstraction и его
клиентов. Это свойство особенно важно, если необходимо
обеспечить двоичную совместимость между разными версиями
библиотеки классов.
Повышение степени расширяемости.
Иерархии классов Abstraction и Implementor можно расширять
независимо.
Сокрытие деталей реализации от клиентов.
Клиенты, пользующиеся интерфейсом Abstraction, изолируются от
деталей реализации этих операций, которые на самом деле
выполняются с помощью вызовов методов Implementor-а. Это
делает возможным прозрачную подмену Implementor-ов как
статически на этапе компиляции, так и динамически.
Пример №1
В качестве примера рассмотрим систему графических
примитивов, которые не должны зависеть от
графической библиотеки.
Определим интерфейс Implementor-а:
public abstract class DrawingApi
{
public abstract void DrawPoint(Point point);
public abstract void DrawLine(
Point startPoint,
Point endPoint);
}
Пример №1
Определим также класс фигуры, который будет
базовым для всех графических примитивов:
public abstract class Shape
{
public abstract void Draw();
}
Пример №1
Класс Line, представляющий линию, будет иметь
следующий вид. Заметим, что реализация метода
рисования тривиальна:
public class Line : Shape {
private readonly DrawingApi drawingApi;
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public Line(Point startPoint, Point endPoint, DrawingApi drawingApi) {
this.drawingApi = drawingApi;
StartPoint = startPoint;
EndPoint = endPoint;
}
public override void Draw() {
drawingApi.DrawLine(StartPoint, EndPoint);
}
}
Пример №1
Класс, представляющий окружность, будет иметь более
сложную реализацию метода рисования:
public class Circle : Shape {
private readonly DrawingApi drawingApi;
public Point Center { get; set; }
public double Radius { get; set; }
public Circle(Point center, double radius, DrawingApi drawingApi) {
this.drawingApi = drawingApi;
Center = center;
Radius = radius;
}
public override void Draw() {
for (int angle = 0; angle < 360; angle++)
drawingApi.DrawPoint(Center + new Size(
(int)Math.Round(Math.Cos(angle * (Math.PI / 180)) * Radius),
(int)Math.Round(Math.Sin(angle * (Math.PI / 180)) * Radius)
)
);
}
}
Пример №1
Классы, которые реализуют абстрактные методы
класса DrawingApi, могут быть такими:
public class OpenGLDrawingApi : DrawingApi {
public override void DrawPoint(Point point) {
// Рисование средствами OpenGL
}
public override void DrawLine(Point startPoint, Point endPoint) {
// Рисование средствами OpenGL
}
}
public class DirectXDrawingApi : DrawingApi {
public override void DrawPoint(Point point) {
// Рисование средствами DirectX
}
public override void DrawLine(Point startPoint, Point endPoint) {
// Рисование средствами DirectX
}
}
Пример №2
Рассмотрим пример занесения данных в базу,
например, восстановление данных из
резервного хранилища.
Существует несколько способов занести
данные в базу:
 при помощи обычных выражений вставки,
 при помощи вставки нескольких строк одним
выражением,
 при помощи вставки данных с обновлением.
Кроме того дизайн системы должен учитывать
особенности представления данных для
каждого сервера баз данных.
Пример №2
Примеры возможных вариантов выражений вставки:
Обыкновенная вставка:
INSERT INTO country (id, name, short_name) VALUES (1, 'Russia', 'RUS');
Вставка нескольких строк одним выражением:
INSERT INTO country (id, name, short_name) VALUES
(1, 'Russia', 'RUS'),
(2, 'United States of America', 'USA');
Вставка с обновлением:
REPLACE INTO country (id, name, short_name) VALUES (1, 'Russia', 'RUS');
Пример №2
Пример №2
Участники

DataDumper ― класс представляющий интерфейс для
переноса данных.

InsertDataDumper, MultiInsertDataDumper,
UpsertDataDumper ― классы осуществляющие
перенос данных одним из указанных выше способов.

ExtractImp ― класс, инкапсулирующий в себе знания
об особенностях представления данных в каком-либо
сервере баз данных.

MySqlExtractImp, SqlServerExtractImp ― классы,
реализующие логику представления данных для
каждого конкретного сервера.
Паттерн Стратегия
(Strategy)
Определение и мотивация
Определение
Стратегия — паттерн поведения объектов, инкапсулирующий
отдельные алгоритмы.
Мотивация

Имеется много родственных классов, отличающихся только
поведением.

Вам нужно иметь несколько разных вариантов алгоритма.
Например, можно определить два варианта алгоритма, один
из которых требует больше времени, а другой — больше
памяти.

В алгоритме содержатся данные, о которых клиент не
должен «знать».

В классе определено много поведений, представленных
разветвленными условными операторами.
Плюсы

Семейства родственных алгоритмов.
Иерархия классов Strategy определяет семейство алгоритмов или
поведений, которые можно повторно использовать в разных контекстах и
приложениях. Наследование позволяет вычленить общую для всех
алгоритмов функциональность.

Альтернатива порождению подклассов.
Использование стратегий позволяет инкапсулировать изменяющееся
поведение в отдельной иерархии классов, что даѐт возможность
исключить необходимость в порождении подклассов для изменения
поведения.

Избавление от серии условных операторов.
Благодаря паттерну стратегия удается отказаться от условных операторов
при выборе нужного поведения. Когда различные поведения помещаются в
один класс, трудно выбрать нужное без применения условных операторов.
Инкапсуляция каждого поведения в отдельный класс Strategy решает эту
проблему.

Выбор реализации.
Стратегии могут предлагать различные реализации одного и того же
поведения. Клиент вправе выбирать подходящую стратегию в зависимости
от своих требований к быстродействию и памяти.
Диаграмма классов
Участники

Strategy — стратегия.
Объявляет общий для всех поддерживаемых стратегий
интерфейс. Класс Context пользуется этим
интерфейсом для вызова конкретного алгоритма,
определенного в классе ConcreteStrategy.

ConcreteStrategy — конкретная стратегия.
Реализует алгоритм, использующий интерфейс,
объявленный в классе Strategy.

Context — контекст.
Конфигурируется объектом класса ConcreteStrategy.
Хранит ссылку на объект класса Strategy. Может
определять интерфейс, который позволяет объекту
Strategy получить доступ к данным контекста.
Пример №1
Рассмотрим приложение, которое позволяет вычислить
сумму инвестиций в некоторый проект разными
способами.
Определим интерфейс стратегии, вычисляющий сумму,
которую необходимо инвестировать:
interface IInvestmentStrategy
{
long CalculateInvestment();
}
Пример №1
Одной из возможных реализаций может быть
стратегия, которая вычисляет сумму инвестиций на
основе фаз Луны:
class FollowTheMoonInvestmentStrategy : IInvestmentStrategy {
public int MoonPhase { get; private set; }
public int AstrologicalSign { get; private set; }
public FollowTheMoonInvestmentStrategy(int moonPhase, int yourSign) {
MoonPhase = moonPhase;
AstrologicalSign = yourSign;
}
public long CalculateInvestment() {
if (MoonPhase == AstrologicalSign)
return 10000;
else
return 100 * (MoonPhase % DateTime.Today.Day);
}
}
Пример №1
Другая реализация стратегии предлагает вложить все
средства, которые имеются на ваших банковских
счетах:
class EverythingYouOwnInvestmentStrategy : IInvestmentStrategy
{
public IBankAccessor Account { get; private set; }
public EverythingYouOwnInvestmentStrategy(IBankAccessor account)
{
Account = account;
}
public long CalculateInvestment()
{
return Account.GetAccountBalancesTotal();
}
}
Пример №1
Класс, использующий стратегию, сообщает
пользователю о сумме необходимых инвестиций:
class InvestmentManager
{
public IInvestmentStrategy Strategy { get; set; }
public InvestmentManager(IInvestmentStrategy strategy)
{
Strategy = strategy;
}
public void Report()
{
var investment = Strategy.CalculateInvestment();
Console.WriteLine("You should invest {0} dollars!", investment);
}
}
Пример №2
В качестве примера стратегии рассмотрим класс, который
для данного арендатора выводит отчѐт о бравшихся в
аренду фильмах, общей сумме задолженности и
количестве очков за активность:
public abstract class Statement
{
public string GetValue(Customer customer)
{
string result = GetHeader(customer);
foreach (Rental rental in customer.Rentals)
result += GetRentalString(rental);
result += GetFooter(customer);
return result;
}
protected abstract string GetFooter(Customer customer);
protected abstract string GetRentalString(Rental rental);
protected abstract string GetHeader(Customer customer);
}
Пример №2
Класс арендатора предоставляет интерфейс для
получения данных, необходимых для создания отчѐта:
public class Customer
{
public IEnumerable<Rental> Rentals
{
get;
}
public string Name { get; set; }
public string GetTotalCharge();
public string GetTotalFrequentRenterPoints();
}
Пример №2
Конкретная реализация стратегии, отвечающая за
вывод информации в текстовом виде:
public class TextStatement : Statement {
protected override string GetHeader(Customer customer) {
return "Учёт аренды для " + customer.Name + "\n";
}
protected override string GetRentalString(Rental rental) {
return "\t" + rental.Movie.Title + "\t" +
rental.Charge.ToString() + "\n";
}
protected override string GetFooter(Customer customer) {
return "Сумма задолженности составляет " +
customer.GetTotalCharge().ToString() + "\n" +
"Вы заработали " +
customer.GetTotalFrequentRenterPoints().ToString() +
" очков за активность";
}
}
Пример №2
Другая реализация стратегии, отвечающая за вывод
информации в виде HTML:
public class HTMLStatement : Statement {
protected override string GetHeader(Customer customer) {
return "<H1>Учёт аренды для <EM>" +
customer.Name + "</EM></H1><P>";
}
protected override string GetRentalString(Rental rental) {
return rental.Movie.Title + "
" +
rental.Charge.ToString() + "<BR>";
}
protected override string GetFooter(Customer customer) {
return "<P>Сумма задолженности составляет <EM>" +
customer.GetTotalCharge().ToString() + "</EM><BR><P>" +
"Вы заработали <EM>" +
customer.GetTotalFrequentRenterPoints().ToString() +
"</EM> очков за активность";
}
}
Пример №2
Класс арендатора реализует метод получения отчета,
делегируя работу классу стратегии:
public class Customer
{
public string GetStatementValue(Statement statement)
{
return statement.GetValue(this);
}
}
Пример №3
Одним из наиболее известных примеров стратегии
является интерфейс IComparer из библиотеки .NET.
public interface IComparer<T>
{
int Compare(T x, T y);
}
Алгоритм сортировки списка может быть
параметризован реализацией интерфейса IComparer:
public class List<T>
{
public void Sort(IComparer<T> comparer);
}
Пример №3
Рассмотрим два примера реализации стратегии
сравнения строк:
class StringLengthComparer : IComparer<string>
{
public int Compare(string x, string y)
{
return x.Length - y.Length;
}
}
class ReverseComparer : IComparer<string>
{
public int Compare(string x, string y)
{
return y.CompareTo(x);
}
}
Пример №3
В данном фрагменте кода демонстрируется
использование класса ReverseComparer для
сортировки списка в обратном порядке:
List<string> names = new List<string>()
{
"Аня", "Марина", "Коля"
};
names.Sort(new ReverseComparer());
foreach (string name in names)
Console.WriteLine(name);
Пример №4
Рассмотрим пример реализации стратегии на языке C++ с
использованием обобщенного программирования.
Источник:
Александреску Андрей. Современное проектирование на С++:
Обобщенное программирование и прикладные шаблоны
проектирования.
Объекты могут быть созданы:



вызовом классического оператора new;
клонированием уже существующего объекта;
использованием расширенной формы оператора new с
предварительным распределением памяти при помощи
функции malloc.
Пример №4
Определим стратегию создания объектов, которая
использует классический оператор new:
template <class T>
struct NewOperatorCreator
{
static T* Create()
{
return new T;
}
};
Пример №4
Стратегия создания объектов, в которой память
распределяется посредством вызова функции malloc:
template <class T>
struct MallocCreator
{
static T* Create()
{
void* buffer = std::malloc(sizeof(T));
if (!buffer)
return new(buffer) T;
}
};
Пример №4
Стратегия, использующая клонирование, выглядит
следующим образом:
template <class T>
class PrototypeCreator {
private:
T* prototype;
public:
PrototypeCreator(T* aPrototype = NULL)
: prototype(aPrototype)
{ }
T* Create() {
return prototype ? prototype->Clone() : NULL;
}
T* GetPrototype() { return prototype; }
void SetPrototype(T* aPrototype) {
prototype = aPrototype;
}
};
Пример №4
Класс, использующий стратегии создания объектов,
содержит в параметрах шаблона класс стратегии:
template <class CreationStrategy>
class WidgetManager : public CreationStrategy
{
public:
void DoSomething()
{
Widget * widget = CreationStrategy().Create();
// ...
}
};
Код, использующий этот класс, при конкретизации
шаблона передаѐт ему необходимую стратегию:
typedef WidgetManager<NewOperatorCreator<Widget> > MyWidgetManager;
Пример №4
Использование шаблонных шаблонных параметров позволяет
избежать указания шаблонного аргумента стратегии в коде,
который использует стратегию:
template <
template <class Created>
class CreationStrategy
>
class WidgetManager : CreationStrategy<Widget> {
public:
void DoSomething() {
Widget * widget = CreationStrategy<Widget>().Create();
// ...
}
};
Такой синтаксис объявления шаблона сообщает компилятору о
том, что шаблонным параметром класса WidgetManager должен
быть шаблонный класс с одним шаблонным параметром.
Пример №4
Теперь код, использующий класс WidgetManager,
вместо спецификации класса стратегии вместе с
шаблонным параметром:
typedef WidgetManager<NewOperatorCreator<Widget> > MyWidgetManager;
должен специфицировать только класс стратегии без
указания типа объектов, которые она создаѐт:
typedef WidgetManager<NewOperatorCreator> MyWidgetManager;
Пример №4
Использование шаблонных шаблонных параметров
позволяет классу, использующему стратегию,
конкретизировать еѐ другим типом:
template <
template <class Created>
class CreationStrategy
>
class WidgetManager : CreationStrategy<Widget>
{
public:
Gadget * CreateGadget()
{
return CreationStrategy<Gadget>().Create();
}
};
Пример №4
Шаблонные параметры допускают указание значений
по умолчанию, что может заметно упростить
использование класса:
template <
template <class Created>
class CreationStrategy = NewOperatorCreator
>
class WidgetManager : CreationStrategy<Widget>
{
};
typedef WidgetManager<NewOperatorCreator> MyWidgetManager;
эквивалентно
typedef WidgetManager MyWidgetManager;
Пример №4
Следующий фрагмент кода демонстрирует
использование расширенного интерфейса стратегии:
template <template <class Created> class CreationStrategy>
class WidgetManager : CreationStrategy<Widget>
{
public:
void SwitchPrototype(Widget * newPrototype)
{
CreationStrategy<Widget>& strategy = *this;
delete strategy.GetPrototype();
strategy.SetPrototype(newPrototype);
}
};
Методы SetPrototype и GetPrototype присутствуют
только в стратегии, использующей клонирование.
Пример №4
Возможность использования расширенных интерфейсов
стратегий обеспечивает следующая особенность
компилятора C++: если функция-член шаблонного класса не
используется — компилятор игнорирует её.
Это означает, что



если пользователь определяет класс WidgetManager с
помощью стратегии PrototypeCreator, можно использовать
функцию SwitchPrototype;
если пользователь определяет класс WidgetManager с
помощью стратегий, не поддерживающих работу с
прототипами, и пытается использовать функцию
SwitchPrototype, возникнет ошибка во время компиляции;
если пользователь определяет класс WidgetManager с
помощью стратегий, не поддерживающих работу с
прототипами, и не использует функцию SwitchPrototype,
программа считается правильной.
Пример №4
Первый фрагмент кода без вызова SwitchPrototype
будет считаться корректным, второй фрагмент
корректен по определению.
WidgetManager<NewOperatorCreator> manager1; // OK
manager1.SwitchPrototype(prototype);
// Compile error!
WidgetManager<PrototypeCreator> manager2;
manager2.SwitchPrototype(prototype);
// OK
// OK
Пример №5
Рассмотрим реализацию класса умного указателя,
который параметризуется несколькими
ортогональными стратегиями:
template
<
class T,
template <class> class CheckingStrategy,
template <class> class ThreadingModel
>
class SmartPtr
{ /* ... */ };
Указать конкретные стратегии можно следующим
образом:
typedef SmartPtr<Widget, NoCheckingStrategy, SingleThreadModel> FastWidgetPtr;
typedef SmartPtr<Widget, CheckNotNullStrategy, MultiThreadModel> SafeWidgetPtr;
Пример №5
Стандарная практика реализации умных указателей
C++ состоит в переопределении оператора ->.
Реализация с использованием стратегий имеет
следующий вид:
template
<
class T,
template <class> class CheckingStrategy,
template <class> class ThreadingModel
>
class SmartPtr : public CheckingStrategy<T>, public ThreadingModel<T> {
private:
T* pointee;
public:
T* operator-> () {
CheckingStrategy<T>::Check(pointee);
return pointee;
}
};
Пример №5
Рассмотрим пример реализации стратегий проверки:
template <class T>
struct NoCheckingStrategy
{
static void Check(T*) { }
};
template <class T>
struct CheckNotNullStrategy
{
class NullPointerException : public std::exception { };
static void Check(T* pointer)
{
if (!pointer)
throw NullPointerException();
}
};
Паттерн
Шаблонный Метод
(Template Method)
Определение и мотивация
Определение
Шаблонный метод (Template method) — паттерн поведения
объектов, определяющий основу алгоритма и позволяющий
наследникам переопределять некоторые шаги алгоритма, не
изменяя его структуру в целом.
Мотивация
 Дублирование инвариантного поведения в подклассах.
 Необходимость контроля расширения поведения в подклассах.
В данном случае шаблонный метод необходим для
определения операций-зацепок (hooks), которые (возможно)
будут изменяться в подклассах.
Плюсы



Однократное использование
инвариантной части алгоритма, с
оставлением изменяющейся части на
усмотрение наследникам.
Локализация и вычленение общего для
нескольких классов кода для избежания
дублирования.
Разрешение расширения кода
наследниками только в определенных
местах.
Диаграмма классов
Участники
AbstractClass ― абстрактный класс: определяет
абстрактные примитивные операции, замещаемые в
конкретных подклассах для реализации шагов
алгоритма; реализует шаблонный метод,
определяющий скелет алгоритма. Шаблонный метод
вызывает примитивные операции, а также операции,
определенные в классе AbstractClass или в других
объектах.
ConcreteClass ― конкретный класс: реализует
примитивные операции, выполняющие шаги алгоритма,
способом, который зависит от подкласса.
Пример №1
Задача: для данного арендатора сформировать текстовую и
гипертекстовую версии отчета, включающего информацию о
бравшихся в аренду фильмах, общей сумме задолженности
и количестве очков за активность.
public class Customer
{
public IEnumerable<Rental> Rentals
{
get;
}
public string Name { get; set; }
public string GetTotalCharge();
public string GetTotalFrequentRenterPoints();
}
Пример №1
Базовый класс, определяющий каркас алгоритма построения
отчета:
public abstract class Statement
{
public string GetValue(Customer customer)
{
string result = GetHeader(customer);
foreach (Rental rental in customer.Rentals)
result += GetRentalString(rental);
result += GetFooter(customer);
return result;
}
protected abstract string GetFooter(Customer customer);
protected abstract string GetRentalString(Rental rental);
protected abstract string GetHeader(Customer customer);
}
Пример №1
Формирование текстовой версии отчета:
public class TextStatement : Statement {
protected override string GetHeader(Customer customer) {
return "Учёт аренды для " + customer.Name + "\n";
}
protected override string GetRentalString(Rental rental) {
return "\t" + rental.Movie.Title + "\t" +
rental.Charge.ToString() + "\n";
}
protected override string GetFooter(Customer customer) {
return "Сумма задолженности составляет " +
customer.GetTotalCharge().ToString() + "\n" +
"Вы заработали " +
customer.GetTotalFrequentRenterPoints().ToString() +
" очков за активность";
}
}
Пример №1
Формирование гипертекстовой версии отчета:
public class HTMLStatement : Statement {
protected override string GetHeader(Customer customer) {
return "<H1>Учёт аренды для <EM>" +
customer.Name + "</EM></H1><P>";
}
protected override string GetRentalString(Rental rental) {
return rental.Movie.Title + "
" +
rental.Charge.ToString() + "<BR>";
}
protected override string GetFooter(Customer customer) {
return "<P>Сумма задолженности составляет <EM>" +
customer.GetTotalCharge().ToString() + "</EM><BR><P>" +
"Вы заработали <EM>" +
customer.GetTotalFrequentRenterPoints().ToString() +
"</EM> очков за активность";
}
}
Пример №2
Пусть есть класс Manufacturing с шаблонным методом:
public class Manufacturing {
// Шаблонный метод
public void MakePart() {
Operation1();
Operation2();
}
protected virtual void Operation1() {
// Поведение по умолчанию для Operation1
}
protected virtual void Operation2() {
// Поведение по умолчанию для Operation2
}
}
Пример №2
В одном из наследников между первой и второй
операцией необходимо выполнить некоторое действие.
Перекроем соответствующим образом Operation2
(аналогично можно перекрыть и Operation1):
public class MyManufacturing : Manufacturing
{
protected override void Operation2()
{
// Код, который должен выполняться
// между операциями 1 и 2
DoSomething();
base.Operation2();
}
}
Пример №2
Если добавление действия между операциями
требуется многим клиентам, то реализация базового
алгоритма может (должна) предусмотреть такую
возможность.
public class Manufacturing
{
// Шаблонный метод
public void MakePart()
{
Operation1();
Hook();
Operation2();
}
protected virtual void Hook()
{ }
}
Download