{
...
float getBMW5CarCost(Car* car) const;
float getToyotaCarCost(Car* car) const;
float getFordCarCost(Car* car) const;
float getVolvoCarCost(Car* car) const;
...
};
В какой-то момент стало ясно, что эту систему надо переделывать (рефакторинг), и было решено создать специальную иерархию классов с единственным виртуальным методом, который и будет считать стоимость:
class CostCalculator
{
public:
virtual ~CostCalculator() {};
virtual float calculateCost(Car* car) const = 0;
};
Далее вы используете какой-нибудь из порождающих шаблонов проектирования, чтобы порождать конкретный класс CostCalculator из конкретного класса машины, – например, для расчета стоимости BMW5 будет использоваться класс BMW5CostCalculator, порожденный от CostCalculator. Но как написать все эти новые классы? Их можно написать заново, но это время, деньги и новые ошибки.
Тут на помощь приходит паттерн «Адаптер». Мы просто вызываем методы из старого класса CarCostCalculatorForAllCarTypes в новых классах. Например, в листинге 1 приведен код для класса BMW5CostCalculator.
Листинг 1
class BMW5CostCalculator: public CostCalculator
{
CarCostCalculatorForAllCarTypes mCalculator;
public:
virtual ~BMW5CostCalculator() {};
virtual float calculateCost(Car* car) const {return mCalculator.getBMW5CarCost(car);}
};
В итоге получается новая иерархия классов, которая легко наследует все методы и функциональность старой системы. Кроме того, в эту новую систему гораздо легче добавлять новые возможности. Например, если вам надо добавить вычисление стоимости автомобиля «Лада», то вам не надо будет менять класс CarCostCalculatorForAllCarTypes, а это большой плюс, поскольку любое изменение такого класса, как CarCostCalculatorForAllCarTypes, который используется повсеместно в коде, – это угроза добавления новых багов.
В общем, главное назначение паттерна «Адаптер» – адаптирование текущего кода под новый интерфейс без необходимости изменения этого старого кода.
«Мост»
Паттерн «Мост» (Bridge) применяется в том случае, если вы хотите разделить реализацию класса и его интерфейс так, чтобы была возможность изменять их независимо друг от друга. Обычно это значит, что этот шаблон стоит применять всегда, когда вы видите класс, который будет изменяться очень часто. Причем неважно, что будет меняться – интерфейс или реализация.
При этом надо отметить, что при использовании наследования реализация жестко привязывается к абстракции, что затрудняет независимую модификацию, поэтому обычное наследование в этом случае не подходит. Основа «Моста» – это разнесение интерфейса и реализации класса в разные иерархии классов. Например, предположим, что наша программа по расчету стоимости автомобиля стала популярна и ее захотели приобрести в другой стране. Сразу же выяснилось, что алгоритм расчета стоимости в другой стране совсем другой. К тому же там появляются совсем другие задачи, например расчет налоговых вычетов при покупке авто в зависимости от возраста и другие не менее интересные и важные расчеты.
И мы, естественно, захотели расширить интерфейс CostCalculator. При этом ясно, что в дальнейшем будет изменяться не только интерфейс, но и реализация.
Можно было бы пойти неверным путем и создавать классы вроде BMW5CostCalculatorRussia и BMW5CostCalculatorFrance, но с ростом числа стран и машин программист просто утонет в ненужной работе.
А значит, применим паттерн «Мост» (см. листинг 2).
Листинг 2
class CostCalculator
{
protected:
CostCalculatorImpl* mImpl;
public:
CostCalculator(CostCalculatorImpl* Impl): mImpl(Impl) {};
virtual ~CostCalculator() {delete mImpl;}
virtual float calculateCost(Car* car) const = 0;
virtual void changeImpl(CostCalculatorImpl* Impl) {delete mImpl; mImpl = Impl;}
};
class CostCalculatorRussia: public CostCalculator
{
public:
CostCalculatorRussia(CostCalculatorImpl* Impl): CostCalculator(Impl) {};
virtual ~CostCalculatorRussia() {}
virtual float calculateCost(Car* car) const
{
return mImpl->calculateCost(car);
}
};
class CostCalculatorFrance: public CostCalculator
{
public:
CostCalculatorFrance(CostCalculatorImpl* Impl): CostCalculator(Impl) {};
virtual ~CostCalculatorFrance() {}
virtual float getTaxDeduction(Car* car) const
{
float carCost = mImpl->calculateCost(car);
// предположим, что по закону всем жителям, покупающим машину, произведенную
// во Франции и стоящую дороже 20 тыс. евро, дается 20 % скидки на нее.
if (carCost > 20000 && car->getFactoryCountry() == «France»)
return carCost*0.20f;
return 0;
}
virtual float calculateCost(Car* car) const