{
float carCost = mImpl->calculateCost(car);
return carCost – getTaxDeduction(car);
}
};
При этом вся иерархия классов CostCalculatorImpl может быть просто перенесена из предыдущего кода без изменений (см. класс BMW5CostCalculator из предыдущего примера). И мы сможем изменять реализацию расчета стоимости автомобиля, не изменяя интерфейс и не трогая алгоритм финального расчета в разных странах.
Одно из самых интересных применений данного паттерна – возможность подменять алгоритм на ходу, прямо во время выполнения программы. Это возможно благодаря тому, что мы должны для этого только пересоздать объект класса-реализации, а не сам класс-интерфейс, который везде используется. Например, мы можем создать один объект класса CostCalculatorFrance, а для расчета стоимости разных автомобилей будем просто подменять у него реализацию методом changeImpl.
«Компоновщик»
«Компоновщик» (Composite) позволяет объединить объекты в древовидную структуру для представления иерархии от частного к целому. Он позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково.
Например, рассмотрим алгоритм расчета стоимости автомобиля. Пусть стоимость вычисляется, как простая сумма стоимостей всех частей. Но некоторые части составные, – например, двигатель состоит из множества деталей и они тоже могут быть составными. Как посчитать стоимость в таком случае? Можно применить паттерн «Компоновщик».
Для этого создадим базовый класс CarPart:
class CarPart
{
protected:
std::vector<CarPart*> mParts;
public:
virtual ~CarPart() {};
virtual float getCost() const = 0;
virtual void addPart(CarPart* part)
{
mParts.push_back(part);
};
virtual void removePart(CarPart* part)
{
mParts.erase(std::find(mParts.begin(), mParts.end(), part));
}
virtual float calculateFullCost() const
{
float summary = getCost();
for(int i = 0; i < (int)mParts.size(); ++i)
summary += mParts[i]->calculateFullCost();
return summary;
}
};
Итак, мы определили класс, который имеет цену и может быть контейнером для других подобъектов. Теперь расчет стоимости автомобиля может быть сделан вызовом одной строки car- >calculateFullCost(), если car породить от CarPart, а все детали тоже от CarPart и добавить их в дерево посредством addPart. В листинге 3 приведен короткий пример инициализации классов Car и Engine. В итоге получается, что мы всегда можем вычислить стоимость детали любой сложности, вызвав всего один метод calculateFullCost(), причем неважно, составной это объект или простой.
Листинг 3
void Car::addEngine(Engine* engine)
{
mEngine = engine;
addPart(engine);
}
void EngineLada::init()
{
mSparkPlugs = new SparkPlug[4] ;
addPart(&mSparkPlugs[0] );
addPart(&mSparkPlugs[1] );
addPart(&mSparkPlugs[2] );
addPart(&mSparkPlugs[3] );
...
}
«Оформитель»
Паттерн «Оформитель» (Decorator) предназначен для динамического подключения дополнительного поведения к объекту. Он предоставляет собой альтернативу созданию подклассов для расширения функциональности объектов, причем без определения подклассов. Паттерн известен также под менее распространенным названием «Обертка» (Wrapper), которое во многом раскрывает суть реализации шаблона.
Обычно он применяется, чтобы незначительно изменить поведение объекта или просто добавить некоторые функции объекту без его изменения.
Например, у нас есть класс Car. И мы хотим добавить возможность вывода в лог информации о вызове каждой функции этого объекта. Это можно сделать, изменив код в классе Car, что не всегда возможно и обычно чревато проблемами. Также мы можем породить новый класс от класса Car, перегрузить все функции в нем и добавить лог там. Но это не всегда сработает, поскольку не все функции могут быть виртуальными. Да и порождать новые объекты не всегда получится. Например, если Car – это базовый класс, от которого порождаются классы вроде BMW5Car, то надо менять иерархию наследования, что неприемлемо.
Тут на помощь приходит паттерн «Оформитель». Мы можем создать специальный класс CarDecorator, у которого будет тот же интерфейс, что и у Car. Кроме того, этот класс будет хранить в себе указатель на объект типа Car, чтобы перенаправлять ему «декорируемые» вызовы CarDecorator – базовый класс. Конкретных декораторов может быть сколько угодно, и все они должны быть порождены от базового. Например, для декоратора, который записывает в лог все вызовы функций Car:
class Car
{
...
float getCost();
virtual void addEngine(Engine* engine);
...
};
class CarDecorator