Andriy Shyrokoryadov

.Net developer, data scientist

Различия между абстрактным классом и интерфейсом - вопрос №7 на собеседование C# / .NET

Текст к видео "Различия между абстрактным классом и интерфейсом" на канале YouTube

На собеседовании часто можно услышать вопрос какая разница между абстрактным классом и интерфейсом. Самое время на него ответить. Ответ можно начать с объяснения ограничений абстрактных классов. Абстрактные классы кроме абстрактных методов могут иметь различное количество членов иного назначения: конструкторы, свойства, конкретные методы. Если мы наследуем абстрактный класс, то кроме абстрактных методов, которые необходимо переопределить, мы получаем доступ к остальным членам базового класса. Это не всегда удобно и не всегда логично. Например, абстрактный класс «Геометрическая фигура» может иметь абстрактный метод «Посчитать количества вершин». Для таких фигур, как «Квадрат» или «Треугольник» нет никаких проблем. Однако если мы посмотрим на фигуру «Окружность», то для неё имплементация метода «Посчитать количества вершин» может быть неоднозначна.

Также в крупных приложениях проблематично создать базовый абстрактный класс, который был бы хорошим решением для большинства ситуаций. А учитывая ограничение, что в языке C# мы можем наследовать только один класс, тяжело выработать общее решение для базового класса, которое подходил бы к большинству случаев.

В таких ситуациях интерфейсы призваны решить наши проблемы. Начнем со второй ситуации. Класс может реализовывать один или несколько интерфейсов, количество интерфейсов неограниченно, но не стоит этим злоупотреблять, чтобы не нарушить один из принципов SOLID – принцип единственной ответственности. Каждый интерфейс может декларировать определенный функционал, а классы могут реализовывать этот функционал имплементируя эти интерфейсы. Тут также стоит вспомнить о принципе разделения интерфейсов (принцип I из сокращения SOLID) – интерфейсы должны быть очень специфичны и не содержать в себе излишне много членов. Если так есть, то скорей всего кроме принципа I нарушается принцип S (единичная ответственность) и скорей всего необходимо провести декомпозицию / разделение интерфейсов.

Что касается первой ситуации, то можно создать несколько интерфейсов определяющие «поведение» геометрических фигур и применять их в зависимости от ситуации, когда это имеет смысл. Например, можно бы было создать интерфейс «У меня есть точки» и реализовать его только в классах «Треугольник» и «Квадрат». Такое «поведение» классу «Окружность» не присуще, поэтому данный класс его не имплементирует.

Подведем итоги:

  • класс может наследовать один (и только один) абстрактный класс;
  • класс может имплементировать несколько интерфейсов;
  • при наследовании абстрактного или конкретного класса мы получаем весь функционал этого класса (кроме членов, обозначенных модификатором доступа private) даже если он не имеет смысла в нашем классе.
  • при имплементации интерфейса мы получаем конкретный список членов, которые должны быть реализованы в нашем классе.