У классов есть возможность наследовать реализацию другого класса. Это значит, что если у нас есть одинаковый код, который должен быть в двух классах, то нам не нужно его копировать из класса в класс, а можно указать, что класс наследует реализацию предка.
Проилюстрируем случай с общим методом:
Что здесь происходит:
- У нас есть базовый класс A с методом methodA
- Нам по какой-то причине понадобился такой же метод в классе B. Мы не копипастим код, а используем ключевое слово extends, которым указываем, что B наследует реализацию A.
- Дальше в Main мы пишем код, который использует класс B.
- Создаем объект класса B
B b = new B(); - Вызываем у этого объекта метод methodA
Вместо наследует можно сказать расширяет функционал класса A.
Тоже самое справедливо для полей:
Что здесь происходит:
- Мы создаем базовый класс A с полем fieldA
- Наследуем это поле в классе-потомке B
- Дальше создаем экземпляр класса B и используем fieldA таким же образом, как будто мы его объявляли ранее в классе B.
Объект и экземпляр - синонимы. Иногда еще говорят инстанс, как калька с английского instance
Модификаторы доступа и наследование
Вернемся к модификаторам доступа.
Кроме видимости для внешнимх классов, они так же влияют на видимость полей и методов для потомков. Члены класса помеченные, как private не видны в коде потомка.
В связи с наследованием добавляется еще промежуточный модификатор protected, когда член класса не виден для внешнего кода (так же как и private), но его можно использовать в коде потомка.
Пример с модификатором protected:
Резюмируя, у нас получается 3 модификатора доступа:
- public - видно во внешнем коде и потомках
- protected - видно в потомках, но не видно во внешнем коде
- private - не видно в потомках, не видно во внешнем коде
Для удобства сведем видимость предка в зависимости от модификатора в таблицу:
Абстрактные классы
Если класс предок нам интересен только с точки зрения переиспользования кода, а объекты такого класса мы не планируем создавать, то можно воспользоваться техникой абстрактных классов.
К определению класса добавляем ключевое слово abstract. Тем самым мы запрещаем создание таких объектов.
Тогда наш первый пример будет выглядеть так:
Композиция и наследование
Под композицией имеется ввиду, что мы не наследуем реализацию, а включаем нужный нам класс ввиде поля и для запроса функционала обращаемся к этому полю.
Перепишем наш пример используя композицию:
либо чуть короче
В последнем примере мы не используем конструктор, а инициализируем поле fieldA прямо во время объявления класса. На самом деле это абсолютно эквивалетный код. Конструктор без параметра генерируется компилятором и в конструкторе происходит инициализация поля fieldA.
На этих “синтетических” примерах код с наследованием выглядит выигрышнее, но, зачастую, бесконтрольное использование наследования даже с благой целью переиспользования кода, приводит к запутанному и тяжело поддерживаемому коду. При проектировании есть смысл еще раз все взвесить и, возможно, отдать предпочтение композиции, а не наследованию.
Когда наследование оправдано
Если ваш проект подразумевает библиотеку классов, у вас достаточно времени, чтобы сделать продуманную иерархию классов и при этом предполагается, что зависимости между классами будут мало меняться в будущем (что уже шаткое предположение), то почему бы и нет. На мой взгляд для наследования хороший кандидат GUI библитека (отображение графического пользовательского интерфейса) - окна, диалоги, кнопки.
Полезные ссылки: