10
160
protected:
//
Что было у папы, но не было у дедушки
public:
virtual void Fn1();
virtual void Fn2(int);
};
class AuntMartha : public Grandpa {
private:
//
Личная жизнь тетушки Марты
protected:
//
То, что она передала моим кузенам
public:
virtual void Fn1();
virtual void Fn2(int);
};
Иерархию можно продолжить и дальше (например,
class Me : public Dad
) при условии, что в
открытый интерфейс не добавляется новых функций.
Мы рассмотрим множество примеров, демонстрирующих полезность гомоморфных иерархий, но
сначала я приведу тройной аргумент в пользу этой концепции.
Взаимозаменяемость производных классов
Тот, кто пришел на объектно-ориентированную вечеринку раньше других, смог войти в историю.
Некто Лисков заработал себе имя на следующей идее: если клиент имеет дело с базовым классом, его
не должно интересовать, какой из производных классов на самом деле выполняет работу. Вы должны
иметь возможность подставить экземпляр любого производного класса вместо экземпляра любого
другого производного класса; клиенты базового класса просто пожимают плечами и продолжают
работать так, словно ничего не произошло. Это называется «подстановочным критерием Лискова».
Знатоки объектно-ориентированного программирования обычно сходятся на том, что это — Хорошая
Мысль.
Строго говоря, данный критерий можно выполнить и без гомоморфизма. Если производный класс
содержит дополнительные открытые функции, их можно просто не вызывать из клиента базового
класса. Постойте-ка… а зачем добавлять открытые функции, если их не использовать? Если в одном
производном классе были добавлены одни скрытые функции, а в другом — другие, со временем в
вашей программе наверняка отыщется точка, в которой их нельзя свободно поменять местами.
Настоящая опасность заключается втом, чо без выполнения этого критерия клиентам придется думать
о производных классах, а не только о базовом классе, который они знают и любят. Если бы в
Dad
присутствовали дополнительные открытые члены, клиента
Grandpa
со временем мог бы спросить свой
объект: «Долой притворство — что ты представляешь собой в действительности?» В итоге было бы
нарушено столько принципов модульного строения и инкапсуляции, что об этом можно было бы
написать целую книгу.
Самый простой способ обеспечить взаимозаменяемость — воспользоваться гомоморфизмом. По
крайней мере, для интерфейсов гомоморфизм обеспечивает взаимозаменяемость по определению,
поскольку клиентам
Grandpa
не придется беспокоиться о существовании других функций, с которыми
им положено работать.
Нормальное наследование
Класс
Grandpa
может содержать не чисто виртуальные функции и переменные и при всем этом
обеспечивать полную взаимозаменяемость. Тем не менее, совпадение интерфейсов еще не означает
взаимозаменяемости объектов. Приходится учитывать действие вторичных эффектов. Предположим,
функция
Fn1()
класса
Grandpa
не является чисто виртуальной:
void Grandpa::Fn1()
Do'stlaringiz bilan baham: |