|
Страница 1 из 2
Сказать по правде, изначально я этой статьи не планировал. Я считал вопросы, которые хочу тут обсудить, тривиальными, не стоящими даже упоминания. Однако в процессе написания статей для этого сайта я поднял в одном из форумов обсуждение множественного наследования. В результате чего выяснилось, что большая часть разработчиков имеет весьма смутное представление о наследовании. И, соответственно, допускает очень много ошибок. Поскольку наследование является одной из важнейших черт ООП (если не самой важной!) – я решил посвятить этому явлению отдельную статью.
* * *
Сначала я хочу разграничить два понятия – объект и класс. Эти понятия постоянно путают. Между тем, они являются центральными в ООП. И знать различия между ними, на мой взгляд, необходимо.
Итак, объект. По сути, это что угодно. Вот кубик лежит. Деревянный, синий. Длина ребра 5 см. Это объект. А вон пирамидка. Пластмассовая, красная. 10 см ребро. Это тоже объект. Что между ними общего? Разные размеры. Разная форма. Разный материал.
Однако, общее у них есть. Прежде всего, и кубик, и пирамидка – правильные многогранники. Т.е. сумма количества вершин и количества граней на 2 больше количества ребер. Далее. У обоих фигур есть грани, ребра и вершины. У обоих фигур есть такая характеристика, как размер ребра. Обе фигуры можно вращать. Обе фигуры можно рисовать. Два последних свойства – это уже поведение. Ну и так далее.
Практика программирования показывает, что с однородными объектами оперировать существенно проще, нежели с разнородными. А поскольку между этими фигурами все-таки есть что-то общее, то возникает желание это общее как-то выделить. Вот тут и выплывает такое понятие как класс.
Итак, определение.
Класс – это описатель общих свойств группы объектов. Этими свойствами могут быть как характеристики объектов (размер, вес, цвет и т.п.), так и поведения, роли и т.п.
Замечание. Слово "всех" (описатель всех свойств) произнесено не было. Что означает, что любой объект может принадлежать к нескольким разным классам.
Возьмем за основу тот же пример с геометрическими фигурами. Самое общее описание – правильный многогранник. Безотносительно размера ребра, количества граней и вершин. Единственное, что мы знаем – что у этой фигуры есть вершины, ребра и грани, и что длины ребер равны.
Дальше. Мы можем конкретизировать описание. Допустим, мы хотим нарисовать этот многогранник. Введем такое понятие как отрисовываемый правильный многогранник. Что нам нужно для рисования? Описание общего способа отрисовки, не зависящего от конкретных координат вершин. Возможно, цвет объекта.
Теперь введем классы Куб и Тетраэдр. Объекты, принадлежащие к этим классам, безусловно являются правильными многогранниками. Единственное отличие – числа вершин, ребер и граней уже жестко фиксированы для каждого из новых классов. Далее, зная вид конкретной фигуры, мы можем дать описание способа отрисовки. А значит, любой объект классов Куб или Тетраэдр также является и объектом класса отрисовываемый правильный многогранник. Налицо иерархия классов.
В этой иерархии мы спускаемся от самого общего описания к наиболее конкретизированному. Заметьте, что объект любого класса также подходит под описание любого более общего класса по иерархии. Такое отношение классов и называется наследованием. Каждый дочерний класс наследует все свойства родительского, более общего, и (возможно) добавляет к этим свойствам какие-то свои. Либо переопределяет какие-то свойства родительского класса.
Здесь я хочу привести цитату из классической уже книги
Гради Буча по объектно-ориентированному дизайну:
Inheritance, therefore, defines an "is a" hierarchy among classes, in which subclass inherits from one or more superclasses. This is in fact the litmus test for inheritance. Given classes A and B, if A "is not a" kind of B, then A shouldn't be a subclass of B.
В переводе это звучит так:
Наследование, таким образом, определяет иерархию "является" между классами, в которой подкласс наследует от одного или более суперклассов. Это, фактически, определяющий тест (дословно – лакмусовый тест, прим. моё) для наследования. Если у нас есть классы А и В и если класс А "не является" разновидностью класса В, то А не должен быть подклассом В.
Дочитавшие до этого места, возможно, недоуменно покрутят пальцем у виска. Первая мысль – это же тривиально! Так и есть. Но если бы вы знали, сколько безумных иерархий наследования я видел! В той дискуссии, о которой я упомянул в самом начале, один из участников совершенно серьезно унаследовал танк от... пулемета!!! На том простом основании, что у танка ЕСТЬ пулемет. И это – самая распространенная ошибка. Наследование путают с агрегированием – включением одного объекта в состав другого. Танк не является пулеметом, он его содержит. И из-за этой ошибки чаще всего и возникает желание воспользоваться множественным наследованием.
Перейдем теперь непосредственно к Java. Что тут есть в плане наследования? В языке есть два типа классов – способные содержать реализацию, и неспособные на это. Вторые называются интерфейсами, хотя по сути – это полностью абстрактные классы.
|