- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - Скотт Майерс
Шрифт:
Интервал:
Закладка:
Идиома NVI не требует, чтобы виртуальные функции обязательно были закрытыми. В некоторых иерархиях классов ожидается, что виртуальная функция, переопределенная в производном классе, будем вызывать одноименную функцию из базового класса (как в примере из правила 27). Чтобы такие вызовы были возможны, виртуальная функция должна быть защищенной, а не закрытой. Иногда она даже может быть открытой (как, например, деструкторы в полиморфных базовых классах – см. правило 7), но к этому случаю идиома NVI уже неприменима.
Реализация паттерна «Стратегия» посредством указателей на функции
Идиома NVI – это интересная альтернатива открытым виртуальным функциям, но с точки зрения проектирования она дает не слишком много. В конце концов, мы по-прежнему используем виртуальные функции для вычисления жизненной силы каждого персонажа. С точки зрения проектирования гораздо более сильным было бы утверждение о том, что вычисление жизненной силы персонажа не зависит от типа персонажа, что такие вычисления вообще не являются свойством персонажа как такового. Например, мы можем потребовать, чтобы конструктору каждого персонажа передавался указатель на функцию, которая вызывалась бы для вычисления его жизненной силы:
class GameCharacter; // опережающее объявление
// функция алгоритма по умолчанию для вычисления жизненной силы персонажа
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf)
{}
int healthValue() const
{ return healthFunc(*this);}
...
private:
HealthCalcFunc healthFunc;
};
Это простой пример применения другого распространенного паттерна проектирования – «Стратегия» (Strategy). По сравнению с подходами, основанными на виртуальных функциях в иерархии GameCharacter, он предоставляет некоторые любопытные возможности, повышающие гибкость:
• Разные экземпляры персонажей одного и того же типа могут иметь разные функции вычисления жизненной силы. Например:
class EvilBadGay: public GameCharacter {
public:
explicit EvilBadGay(HealthCalcFunc hcf = defaultHealthCalc)
: GameCharacter(hcf)
{...}
...
};
int loseHealthQuickly(const GameCharacter&); // функции вычисления
int loseHealthSlowly(const GameCharacter&); // жизненной силы
// с разным поведением
EvilBadGay ebg1(loseHealthQuickly); // однотипные персонажи
EvilBadGay ebg2(loseHealthSlowly); // с разным поведением
// относительно здоровья
• Функция вычисления жизненной силы для одного и того же персонажа может изменяться во время исполнения. Например, класс GameCharacter мог бы предложить функцию-член setHealthCalculator, которая позволяет заменить текущую функцию вычисления жизненной силы.
С другой стороны, тот факт, что функция вычисления жизненной силы больше не является функцией-членом иерархии GameCharacter, означает, что она не имеет специального доступа к внутреннему состоянию объекта, чью жизненную силу вычисляет. Например, defaultHealthCalc не имеет доступа к закрытым частям EvilBadGay. Это не страшно, если жизненная сила персонажа может быть вычислена с помощью его открытого интерфейса, но для максимально точных расчетов может понадобиться доступ к закрытой информации. На самом деле такая проблема может возникать всегда, когда некоторая функциональность выносится из класса наружу (например, из функций-членов в свободные функции, не являющиеся друзьями класса, или в функции-члены другого класса, не дружественного данному). Она будет встречаться в настоящем правиле и далее, потому что все прочие проектные решения, которые нам еще предстоит рассмотреть, тоже включают использование функций, находящихся вне иерархии GameCharacter.
Общее правило таково: единственный способ рарешить функциям, не являющимся членами класса, доступ к его закрытой части – ослабить степень инкапсуляции. Например, класс может объявлять функции-нечлены в качестве друзей либо предоставлять открытые функции для доступа к тем частям реализации, которые лучше было бы оставить закрытыми. Имеет ли смысл жертвовать инкапсуляцией ради выгоды от использования указателей на функции вместо виртуальных функций (например, чтобы иметь разные функции жизненной силы для разных объектов и динамически менять их), решать вам в каждом конкретном случае.
Реализация паттерна «Стратегия» посредством класса tr::function
Если вы привыкли к шаблонам и их применению для построения неявных интерфейсов (см. правило 41), то применение указателей на функции покажется вам не слишком гибким решением. Почему вообще для вычисления жизненной силы нужно обязательно использовать функцию, а не что-то ведущее себя как функция (например, функциональный объект)? Если от функции никуда не деться, то почему не сделать ее членом класса? И почему функция должна возвращать int, а не объект, который можно преобразовать в int?
Эти ограничения исчезают, если вместо указателя на функцию (подобную healthFunc) воспользоваться объектом типа tr::function. Как объясняется в правиле 54, такой объект может содержать любую вызываемую сущность (указатель на функцию, функциональный объект либо указатель на функцию-член), чья сигнатура совместима с ожидаемой. Вот пример такого подхода, на этот раз с использованием tr1::function:
class GameCharacter; // как раньше
int defaultHealthCalc(const GameCharacter& gc); // как раньше
class GameCharacter {
public:
// HealthCalcFunction – это любая вызываемая сущность, которой можно
// передать в качестве параметра нечто, совместимое с GameCharacter,
// и которая возвращает нечто, совместимое с int; подробности см. ниже
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf)
{}
int healthValue() const
{ return healthFunc(*this);}
...
private:
HealthCalcFunc healthFunc;
};
Как видите, HealthCalcFunc – это typedef, описывающий конкретизацию шаблона tr1::function. А значит, он работает как обобщенный указатель на функцию. Посмотрим внимательнее, как определен тип HealthCalcFunc:
std::tr1::function<int (const GameCharacter&)>
Здесь я выделил «целевую сигнатуру» данной конкретизации tr1::function. Словами ее можно описать так: «функция, принимающая ссылку на объект типа const GameCharacter и возвращающая int». Объект типа HealthCalcFunc может содержать любую вызываемую сущность, чья сигнатура совместима с заданной. Быть совместимой в данном случае означает, что параметр можно неявно преобразовать в const GameCharacter&, а тип возвращаемого значения неявно конвертируется в int.
Если сравнить с предыдущим вариантом дизайна (где GameCharacter включал в себя указатель на функцию), то вы не обнаружите почти никаких отличий. Единственная разница в том, что GameCharacter теперь содержит объект типа tr1::function – обобщенный указатель на функцию. Это изменение так незначительно, что я назвал бы его несущественным, если бы не то обстоятельство, что теперь пользователь получает ошеломляющую гибкость в спецификации функций, вычисляющих жизненную силу:
short calcHealth(const gameCharacter&); // функция вычисления
// жизненной силы;
// она возвращает не 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.

