Рубрики
Основы Программирование

Абстрактные классы и интерфейсы в C#

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

Для того чтобы понять в чём разница давайте определимся с понятиями.

Абстрактный класс – это класс, содержащий один или несколько абстрактных методов. Особенностью такого класса является то, что нельзя создать объект данного класса. Это связано с тема, что абстрактный метод, как правило, не имеет реализации. Чтобы методы был абстрактным его следует пометить ключевым словом abstract, также класс должен быть тем же служебным словом. Абстрактные классы обычно используются для определения базового класса в иерархии классов.

// Абстрактный класс
public abstract class BaseClass
{
    // Абстрактный метод
    public abstract void SomeMethod();

    public void OtherMethod()
    {
        Console.WriteLine("Other method in BaseClass");
    }
}

// Унаследованный класс
public class ChildClass : BaseClass
{
    // Унаследованный метод
    // 
    public override void SomeMethod()
    {
        Console.WriteLine("Some method in ChildClass");
    }
}

Для того чтобы реализовать метод необходимо в дочернем классе объявить метод с той же сигнатурой, но заменить при этом ключевое словно abstract на override.

class HelloWorld
{
    static void Main()
    {
        BaseClass obj = new ChildClass();
      
        obj.SomeMethod();
      
        obj.OtherMethod();
    }
}

Таким образом при вызове созданного объекта мы сможем вызвать как метод, объявленный в базовом, так и в дочернем классе. В итоге, для нашего примера мы получим следующий вывод:

Теперь поговорим об интерфейсах.

Интерфейс – это абстрактный ссылочный тип, который может содержать некоторое количество методов, свойств, событий и индексаторов. Начиная с версии C# 8.0 ещё и статические поля и константы. Также как и абстрактный класс нельзя создать объект данного типа, но мы можем объявить, например, сигнатуры нужных нам методов.

Чтобы объявить интерфейс достаточно использовать служебное слово interface вместо class. Затем создадим класс который будет наследовать интерфейс и реализовывать его.

// Интерфейс
public interface IBaseInterface
{
    public void SomeMethod();

    public void OtherMethod();
}

// Унаследованный класс
public class ChildClass : IBaseInterface
{
    public void SomeMethod()
    {
        Console.WriteLine("Some method in ChildClass");
    }

    public void OtherMethod()
    {
        Console.WriteLine("Other method in ChildClass");
    }
}

Обратите внимание, что в отличии от абстрактного метода мы должны реализовать здесь все методы, сигнатуры которых объявлены в интерфейсе, при это служебное слово override не требуется. Ну или почти все, но об это чуть позже. В итоге мы получим уже такой вывод:

И вот до версии C# 8.0 тут можно было бы сделать вывод, что абстрактный класс отличается от интерфейса тем, что может содержать в себе реализацию метода и тем, что от интерфейса и тем что класс может быть унаследован от только от одного класса, но реализовывать несколько интерфейсов одновременно.

Но, что если в более новой версии C# мы попробуем сделать вот так?

// Интерфейс
public interface IBaseInterface
{
    public void SomeMethod();

    public void OtherMethod();

    public void DefaultMethod()
    {
        Console.WriteLine("Default method in IBaseInterface");
    }
}

// Унаследованный класс
public class ChildClass : IBaseInterface
{
    public void SomeMethod()
    {
        Console.WriteLine("Some method in ChildClass");
    }

    public void OtherMethod()
    {
        Console.WriteLine("Other method in ChildClass");
    }
}

class HelloWorld
{
    static void Main()
    {
        IBaseInterface obj = new ChildClass();
      
        obj.SomeMethod();
      
        obj.OtherMethod();

        obj.DefaultMethod();
    }
}

Что ж, в таком случае мы увидим, что метод объявленный и реализованный в самом интерфейсе вполне себе будет доступен для использования:

Но в чём тогда смысл? Во-первых, разница в наследовании так и осталась. Мы не можем унаследоваться сразу от двух и более классов, но можем реализовать сразу нескольких интерфейсов. Во-вторых, если у нас имеется большее количество классов, которые уже реализуют интерфейс который мы хотим дополнить каким-нибудь методом, то у нас нет необходимости трогать каждый из них. Это безусловно удобно при работе с legacy-кодом, но не стоит бездумно использовать эту возможность где попало. Кроме того, если мы создадим объект указав его тип как интерфейс, а не дочерний класс, то уже не сможем обратиться к методу реализованному по умолчанию, в отличии от метода который реализован в абстрактном классе, но не переопределён в дочернем. Ну и по прежнему мы не можем объявить конструктор, деструктор и не публичные члены в интерфейсе (поскольку интерфейс реализуется, а не наследуется). Также стоит отметить, что статические члены обязательно требуют реализации как в случае абстрактного класса, так и в случае интерфейса.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *