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

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

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

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

Так вот, когда программист обдумывает иерархию классов, на её вершине оказываются максимально обобщенные сущности, которые, зачастую, полны абстракции, т.е. мы можем описать только их поведение, но не можем конкретизировать его (это поведение). Почему, да потом, что от одной такой сущности, может быть унаследован не один класс, и если мы конкретизируем поведение в базовой сущности, то оно может подойти не для всех наследников… Поэтому и были придуманы абстрактные классы, в которых некоторые (или даже все) методы не имеют тела (т.е. реализации, по сути, конкретики) и интерфейсы, которые вообще не могут иметь реализации.

И вот тут начинается трудный выбор, что использовать в качестве «базы» абстрактный класс или интерфейс? И наверное, однозначного ответа, из серии «поступайте всегда так» всё-таки и нет! Но всё же, больше плюсов на стороне интерфейсов. Почему, давайте разберемся на практике, вспомним пример из статьи, где я рассказывал про абстрактные классы:

//Базовый класс, описывающий оружие (теперь абстрактный)
abstract class Weapon
{
    //Атака оружием
    public abstract void Attack();
}
 
//Класс-наследник оружия, описывает нож
class Knife : Weapon
{
    //Атака ножом (теперь переопределенный метод, добавили override)
    public override void Attack()
    {
        Console.WriteLine("Knife Attack!");
    }
}
 
//Класс-наследник оружия, описывает ружьё
class Gun : Weapon
{
    //Атака ружьем (теперь переопределенный метод, добавили override)
    public override void Attack()
    {
        Console.WriteLine("Gun Attack!");
    }
}

Вот в этом примере, лучше использовать вместо абстрактного класса «Weapon» интерфейс (как собственно, мы и делали в уроке № 24 базового курса). Почему, ну смотрите, класс «Weapon» не содержит ни какой конкретики, все его методы (а точнее один метод) являются абстрактными, т.е. по сути, класс выполняет функцию интерфейса, вот только используя его в качестве базового, мы закрываем единственную вакантную позицию для класса наследника (у класса не может быть больше одного родительского класса).

И суть здесь именно в отличиях классов от интерфейсов, интерфейсы описывают только модель поведения, а вот классы, могут описывать еще конкретную реализацию, так что если Вы понимаете, что ни какой конкретики в базовой сущности быть не должно, то смело используйте интерфейс а не класс в качестве «базы» для других классов.

Опять таки, если Вы понимаете, что у класса может быть больше одного «родителя», то классом может быть только один из них (и тут тоже важно правильно определить какой, если вообще нужно использовать классы).

Но бывают случаи, когда использование классов в качестве «родителей» для других действительно может быть оправдано. Например, мы можем прийти к выводу, что у каждого оружия обязательно должно быть описание (его мы будем хранить в поле базового класса), ну и нужна возможность получить доступ к этому описанию (для этого, мы сделаем свойство). Тогда, базовый класс «Weapon» выглядел бы примерно так:

//Базовый класс, описывающий оружие (теперь абстрактный)
abstract class Weapon
{
    //Атака оружием
    public abstract void Attack();

    //Открытое, не абстрактное свойство
    public string Description 
    {
        get { return description; }
        set { description = value; }
    }

    //Закрытое, не абстрактное свойство
    private string description = "Без описания";
}

И таким образом, все классы наследники «Weapon» получат поле «description» и свойство «Description» по наследству. Т.е. нам не нужно будет дублировать эти сущности в каждом классе наследнике. С одной стороны, это очень удобно, но вот только в определенный момент, мы может понять, что у какого-то наследника не должно быть поля «description» или свойство «Description» должно работать как-то иначе! И это будет неприятным сюрпризом…

Так вот, подводя итоги, я бы посоветовал использовать классы вместо интерфейсов в качестве «базы» для других, только в тех случаях, когда Вы понимаете, что интерфейсом в этом конкретном случае точно не обойтись!

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