Может ли кто-нибудь дать мне простой пример, чтобы доказать, что наследование для реализации является злом?
Иллюстрация ниже является примером в моем слайде лекции, я знаю о C++
И для меня с некоторыми рубиновыми знаниями, Вектор как Array
со случайным доступом, а стек - без, так что, кажется, наследование для меня разумно. так кто-нибудь может дать мне более простой пример? или объяснение что не так с моим пониманием?
2 ответа
Вот ключ к причине, почему наследование не является хорошей идеей в этом случае:
Vector is like Array with random access and Stack is the one without
Вообще говоря, наследование для вас, чтобы обеспечить дополнительное поведение; нет разумного * способа удалить поведение.
Так что если Stack
продолжается Vector
он унаследует поведение произвольного доступа (и контракт), предоставленное Vector
класс, который вы не хотите.
Думайте о наследовании в терминах is-a **. Имеет ли смысл говорить Stack
это Vector
? Если нет, то это не должно распространяться Vector
,
Тот факт, что вы можете реализовать Stack
используя Vector
не имеет отношения к Stack
интерфейс. Как отметил @Patashu, вы хотите иметь возможность менять реализацию, если вам нужно, без изменения контракта. Может быть, например, вы хотите использовать реализацию связанного списка, а не Vector
, Если вы продлите Vector
это невозможно; если вы используете композицию, это довольно тривиально.
* Конечно, вы можете переопределить методы, чтобы удалить нежелательное поведение, но это нарушит Vector
контракт и сделает вещи очень запутанными для ваших пользователей. Вот почему это обычно не разумно.
** is-a имеет свои подводные камни, как и все остальное в архитектуре программного обеспечения: посмотрите на проблему Circle-Ellipse для хорошего примера того, почему вам нужно быть очень осторожным, когда решаете, какой класс наследует от чего.
Это помогает думать об этом с точки зрения кодовой базы, которая пишется для других людей, и поэтому должна поддерживать использование других людей, или если ВАШ код использует упомянутую другую кодовую базу. Когда это всего лишь ваш код, вы можете легко вносить изменения, потому что вы контролируете все, но если другие люди должны быть поддержаны, вы должны знать, какие изменения могут сделать код других людей взломанным.
1) Если вы наследуете от класса, вы должны поддерживать тот же контракт, что и у класса. Под контрактом я подразумеваю, что общедоступные методы формируют набор обещаний, которые класс может выполнить. Это может быть плохо, если а) его контракт громоздок для вашего класса; б) класс, от которого вы унаследовали контракт, изменится из-под вас.
2) Составляя вместо наследования, вы можете изменить то, что вы используете для реализации вашего класса, не внося никаких открытых изменений, и, следовательно, намного быстрее и дешевле, например, если вы решили: "Я не хочу использовать Vector для моей реализации стека". Я хочу использовать DifferentVector вместо этого - это нарушит код, если предположить, что ваш стек является подклассом Vector.
3) Кроме того, даже если вы не используете наследование для своего стека, который поддерживается вектором, вы все равно можете использовать и стек, и вектор, реализующий интерфейс ICollection (например), позволяющий использовать их взаимозаменяемо и полиморфно, что является главным преимуществом наследования в первую очередь.