// жизненной силы;
// она возвращает не int
stuct HealthCalculator { // класс функциональных
int operator()(const GameCharacter&) const // объектов, вычисляющих
{...} // жизненную силу
};
class GameLevel {
public:
float health(const GameCharacter&) const; // функция-член для
... // вычисления жизненной
}; // силы; возвращает не int
class EvilBadGay: public GameCharacter { // как раньше
...
};
class EyeCandyCharacter: public GameCharacter { // другой тип персонажей;
... // предполагается такой же
}; // конструктор как
// у EvilBadGay
EvilBadGay ebg1(calcHealh); // персонаж использует
// функцию вычисления
// жизненной силы
EyeCandyCharacter ecc1(HealthCalculator()); // персонаж использует
// функциональный объект
// вычисления жизненной
// силы
GameLevel currentLevel;
...
EvilBadGay ebg2( // персонаж использует
std::tr1::bind(&GameLevel::health, // функцию-член для
currentLevel, // вычисления жизненной
_1) // силы; подробности
); // см. ниже
Лично я поражаюсь тому, какие удивительные вещи позволяет делать шаблон tr1::function. Если вы не разделяете моих чувств, то не исключено, что просто не понимаете, для чего используется tr1::bind в определении ebg2. Позвольте мне объяснить.
Мы хотим сказать, что для вычисления жизненной силы персонажа ebg2 следует использовать функцию-член класса GameLevel. Но из объявления GameLevel::health следует, что она должна принимать один параметр (ссылку на GameCharacter), а на самом деле она принимает два, потому что имеется еще неявный параметр типа GameLevel – тот, на который внутри нее указывает this. Все функции вычисления жизненной силы принимают лишь один параметр: ссылку на персонажа GameCharacter, чья жизненная сила вычисляется. Если мы используем функцию GameLevel::health, то должны каким-то образом «адаптировать» ее, чтобы вместо двух параметров (GameCharacter и GameLevel) она принимала только один (GameCharacter). В этом примере мы хотим для вычисления здоровья ebg2 в качестве параметра типа GameLevel всегда использовать объект currentLevel, поэтому «привязываем» его как первый параметр при вызове GameLevel::health. Именно в этом и заключается смысл вызова tr1::bind: указать, что функция вычисления жизненной силы персонажа ebg2 должна в качестве объекта типа GameLevel использовать currentLevel.
Я пропускаю целый ряд подробностей, к примеру: почему «_1» означает «использовать currentLevel в качестве объекта GameLevel при вызове GameLevel::health для ebg2». Эти детали не столь сложны, к тому же они не имеют прямого отношения к основной идее, которую я хочу продемонстрировать, а именно: используя tr1::function вместо указателя на функцию, мы позволяем пользователям применять
«Классический» паттерн «Стратегия»
Если вас больше интересуют паттерны проектирования, чем собственно язык C++, то более традиционный подход к реализации паттерна «Стратегия» состоит в том, чтобы сделать функцию вычисления жизненной силы виртуальной функцией-членом в классах, принадлежащих отдельной иерархии. Эта иерархия может выглядеть примерно так:
Если вы не знакомы с нотацией UML, поясню: здесь говорится, что GameCharacter – корень иерархии, в которой EvilBadGay и EyeCandyCharacter являются производными классами; HealthCalcFunc – корень иерархии, в которой производными классами являются SlowHealthLooser и FastHealthLooser; и каждый объект типа GameCharacter содержит указатель на объект из иерархии HealthCalcFunc. А вот как структурируется соответствующий код:
class GameCharacter; // опережающее объявление
class HealthCalcFunc {
public:
...
virtual int calc(const GameCharacter& gc) const
{...}
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public: