Наследование - это очень мощный механизм повторного использования кода. Но нужно правильно использовать. Я бы сказал, что наследование используется правильно, если подкласс также является подтипом родительского класса. Как упоминалось выше, ключевой момент здесь - принцип подстановки Лискова.
Подкласс не совпадает с подтипом. Вы можете создать подклассы, которые не являются подтипами (и это когда вы должны использовать композицию). Чтобы понять, что такое подтип, давайте начнем объяснять, что такое тип.
Когда мы говорим, что число 5 имеет тип integer, мы утверждаем, что 5 принадлежит множеству возможных значений (в качестве примера, посмотрите возможные значения для примитивных типов Java). Мы также утверждаем, что существует допустимый набор методов, которые я могу выполнить для значения, такие как сложение и вычитание. И, наконец, мы заявляем, что существует набор свойств, которые всегда выполняются, например, если я добавлю значения 3 и 5, я получу 8 в результате.
Чтобы привести еще один пример, подумайте об абстрактных типах данных, Набор целых чисел и Список целых чисел, значения, которые они могут содержать, ограничены целыми числами. Они оба поддерживают набор методов, таких как add (newValue) и size (). И оба они имеют разные свойства (инвариант класса), Наборы не допускают дублирования, в то время как List допускает дублирование (конечно, есть и другие свойства, которым они оба удовлетворяют).
Подтип также является типом, который имеет отношение к другому типу, называемому родительским типом (или супертипом). Подтип должен удовлетворять признакам (значениям, методам и свойствам) родительского типа. Отношение означает, что в любом контексте, где ожидается супертип, его можно заменить на подтип, не влияя на поведение выполнения. Давайте посмотрим код, чтобы проиллюстрировать то, что я говорю. Предположим, я пишу Список целых чисел (на каком-то псевдо-языке):
class List {
data = new Array();
Integer size() {
return data.length;
}
add(Integer anInteger) {
data[data.length] = anInteger;
}
}
Затем я записываю Набор целых чисел как подкласс Списка целых чисел:
class Set, inheriting from: List {
add(Integer anInteger) {
if (data.notContains(anInteger)) {
super.add(anInteger);
}
}
}
Наш класс Набор целых чисел является подклассом Списка Целых чисел, но не является подтипом, поскольку он не удовлетворяет всем функциям класса Списка. Значения и сигнатура методов выполняются, а свойства - нет. Поведение метода add (Integer) было явно изменено, без сохранения свойств родительского типа. Подумайте с точки зрения клиента ваших классов. Они могут получить набор целых чисел, где ожидается список целых чисел. Клиент может захотеть добавить значение и добавить это значение в список, даже если это значение уже существует в списке. Но она не получит такого поведения, если ценность существует. Большой сюрприз для нее!
Это классический пример ненадлежащего использования наследства. Используйте композицию в этом случае.
(фрагмент из: правильно использовать наследование ).