October 2, 2023

Абстрактный класс vs Интерфейс

Что в каких случаях использовать

Абстрактные классы и интерфейсы — это два механизма в ООП, которые мы используем при разработке фич. Они служат для определения контрактов или шаблонов для производных классов, но имеют разные особенности и применяются в разных ситуациях.

Абстрактные классы я использую, когда у меня есть семейство классов с общими полями и логикой. Под семейством классов я подразумеваю группу классов, которые имеют схожие характеристики, поведения и расположение в системе. В качестве примера приведу семейство апгрейдов, которые прокачивают различные характеристики игрока (рис. 1-2).

Рис. 1.
Рис. 2.

В данном примере видим, что абстрактный класс HeroUpgrade, имеет общие поля: level, id и hero; общую логику: метод RiseLevel и событие OnLevelUp. Логика самой прокачки статов делегируется классам наследникам в методе LevelUp. С точки зрения расположения файлов, классы апгрейдов лежат рядом друг с другом. При этом будет ошибкой сделать еще один интерфейс IHeroUpgrade, так как это лишний слой абстракции (рис 3).

Рис. 3.

Интерфейсы я использую, когда хочу сделать универсальную точку взаимодействия с классами, которые имеют разную структуру и выполняют разные ответственности. Например, интерфейс ILoadingTask, который выполняет задачу при загрузке приложения (рис 4-5).

Рис. 4.
Рис. 5.

В данном примере, мы видим, что реализации ILoadingTask выполняют абсолютно разные ответственности и относятся к разным модулям программы, но благодаря интерфейсу эти классы имеют общую точку взаимодействия и вызываются друг за другом в ApplicationLoader.

Таким образом, абстрактные классы используются для объединения классов в семейство, у которых есть общие поля, логика и один предок. Интерфейсы используются, когда классы с разной структурой и логикой имеют одинаковые точки соприкосновения. Абстрактные классы наследуют, а интерфейсы — реализуют!

Еще хотел бы добавить, что не нужно делать интерфейсы для всех классов на каждый чих, чтобы обеспечить “максимальную гибкость”. Интерфейсы нужно делать тогда, когда есть несколько реализаций, которые нужно подменять в процессе работы программы. Тоже самое хотел бы сказать про абстрактные классы. Не нужно делать несколько этажей абстрактных классов. Это усложняет понимание кода и его поддержку. Дерево наследования должно быть максимально простым и компактным.

Поэтому следуйте принципу Keep It Simple и все будет гуд! 😉