Считается что объектно-ориентированное программирование основано на трех столпах, которые предоставляют программисту преимущества по сравнению с процедурным подходом. Ими являются — инкапсуляция, наследование и полиморфизм. Наследование позволяет при использовании объектно-ориентированного подхода существенно избавиться от дублирования кода. Но используя его, всегда стоит помнить про один из принципов SOLID, называющийся Liskov Substitution, который, если особо не вдаваться в подробности, гласит, что наследование должно использоваться только как реализация отношения «является». То есть класс потомок должен «являться» подвидом родителя. Например у вас может быть класс Bird (птица) и его подклассы Sparrow (воробей) и Raven (ворон), которые, например, наследуют (и могут расширять или изменять) метод fly. Однако, если же вы создадите потомка Bird, называющегося Penguin (пингвин) и его метод fly, например, будет бросать исключение (потому что пингвины не летают), это будет нарушать принцип Liskov Substitution. Говоря о наследовании, также стоит упомянуть один важный принцип, которого советует придерживаться «Банда четырёх» (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) в своей книге Design Patterns: Elements of Reusable Object-Oriented Software (1994), который гласит, что нужно стараться использовать композицию (когда один объект «содержит» другой объект) вместо наследования настолько, насколько возможно. Собственно многие из паттернов проектирования придуманных «Бандой четырёх» как раз основаны на этом принципе.
Вообще на практике наследование используется не сказать чтобы так часто, даже не смотря на то, что в большинстве случаев хорошие программисты заменяют его композицией. Чаще встречаются множественные реализации одного интерфейса, который в качестве зависимости указываются в клиентских классах. Что в свою очередь позволяет воспользоваться вторым столпом ООП — полиморфизмом. Который позволяет передавать разные реализации зависимостей в клиентский код. Из-за того, что передаваемые зависимости имеют одинаковый интерфейс, клиенту (зависящему от интерфейса) всё равно какой к нему попал объект и это позволяет программисту изменять, во время выполнения программы то, каким способом (какой реализацией интерфейса) будет решена окончательная задача. Говоря об ООП, также нельзя не упомянуть об инкапсуляции. Это важнейший элемент ООП, который предоставляет возможность создавать абстракции, чтобы скрыть низкоуровневые детали реализации снижая сложность разработки. Детали реализации скрываются модификаторами доступа к методам, такими как private и protected. Создавая классы, надо создавать их так, чтобы их интерфейсы (доступные public методы) предоставляли согласованную абстракцию, иначе это уже не ООП(!). Например, давайте предположим, что мы разрабатываем программу, управляющую системой охлаждения ядерного реактора. Тогда согласованная абстракция интерфейса может выглядеть следующим образом.
Благодаря краткому выразительному интерфейсу образующему согласованную абстракцию, мы можем работать с системой охлаждения реактора, не имея ни малейшего представления о низкоуровневых деталях реализации, характерных для выбранной технологии. Использование абстракций, позволяет существенно снизить сложность разрабатываемой системы. А борьба со сложностью является главным техническим императивом разработки. Вот ещё несколько примеров согласованных абстракций:
Система регулирования скорости:
Кофемолка:
Топливный бак:
Фонарь:
Проектируя интерфейс объекта объявляйте публичными только те методы, которые нужны клиенту для управления, скрывая всё остальное. В результате у вас должны получаться чёрные ящики, о устройстве которых можно забыть после того как код класса дописан. Этот подход позволяет существенно снизить сложность разрабатываемой системы.
Вообще на практике наследование используется не сказать чтобы так часто, даже не смотря на то, что в большинстве случаев хорошие программисты заменяют его композицией. Чаще встречаются множественные реализации одного интерфейса, который в качестве зависимости указываются в клиентских классах. Что в свою очередь позволяет воспользоваться вторым столпом ООП — полиморфизмом. Который позволяет передавать разные реализации зависимостей в клиентский код. Из-за того, что передаваемые зависимости имеют одинаковый интерфейс, клиенту (зависящему от интерфейса) всё равно какой к нему попал объект и это позволяет программисту изменять, во время выполнения программы то, каким способом (какой реализацией интерфейса) будет решена окончательная задача. Говоря об ООП, также нельзя не упомянуть об инкапсуляции. Это важнейший элемент ООП, который предоставляет возможность создавать абстракции, чтобы скрыть низкоуровневые детали реализации снижая сложность разработки. Детали реализации скрываются модификаторами доступа к методам, такими как private и protected. Создавая классы, надо создавать их так, чтобы их интерфейсы (доступные public методы) предоставляли согласованную абстракцию, иначе это уже не ООП(!). Например, давайте предположим, что мы разрабатываем программу, управляющую системой охлаждения ядерного реактора. Тогда согласованная абстракция интерфейса может выглядеть следующим образом.
CoolingSystem::getTemperature()
CoolingSystem::SetCirculationRate($rate)
CoolingSystem::OpenValve($valveNumber)
CoolingSystem::CloseValve($valveNumber)
Благодаря краткому выразительному интерфейсу образующему согласованную абстракцию, мы можем работать с системой охлаждения реактора, не имея ни малейшего представления о низкоуровневых деталях реализации, характерных для выбранной технологии. Использование абстракций, позволяет существенно снизить сложность разрабатываемой системы. А борьба со сложностью является главным техническим императивом разработки. Вот ещё несколько примеров согласованных абстракций:
Система регулирования скорости:
- Задать скорость
- Получить текущие параметры
- Восстановить предыдущее значение скорости
- Отключить систему
Кофемолка:
- Включить
- Выключить
- Задать скорость
- Начать перемалывание кофе
- Прекратить перемалывание кофе
Топливный бак:
- Заполнить топливный бак
- Слить топливо
- Получить емкость топливного бака
- Получить статус топливного бака
Фонарь:
- Включить
- Выключить
Проектируя интерфейс объекта объявляйте публичными только те методы, которые нужны клиенту для управления, скрывая всё остальное. В результате у вас должны получаться чёрные ящики, о устройстве которых можно забыть после того как код класса дописан. Этот подход позволяет существенно снизить сложность разрабатываемой системы.