Перейти к содержимому
Теория · Наследование и интерфейсы · урок 6 из 10

Абстрактные классы

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

У любого транспорта есть, например, наибольшая скорость. Но назвать её для «транспорта вообще» нельзя: у легковой она одна, у велосипеда другая. Обычный класс Vehicle заставил бы вписать в метод MaxSpeed какое-то число наугад. Нужен способ сказать: «Этот метод есть у всех, а тело пусть напишет каждый вид».

C#
Vehicle car = new Car("Lada");
Console.WriteLine(car.Describe()); // Lada: 180 km/h

abstract class Vehicle
{
    public string Name;
    protected Vehicle(string name) { Name = name; }
    public abstract int MaxSpeed();
    public string Describe() => $"{Name}: {MaxSpeed()} km/h";
}
class Car : Vehicle
{
    public Car(string name) : base(name) { }
    public override int MaxSpeed() => 180;
}

Давайте разберём по строкам:

  1. abstract class Vehicle объявляет класс, объект которого создать нельзя: он нужен только как базовый.
  2. public abstract int MaxSpeed(); — метод без тела: вместо фигурных скобок стоит точка с запятой.
  3. Describe — обычный метод с телом. Он вызывает MaxSpeed, не зная, каким окажется вид транспорта.
  4. Car переопределяет MaxSpeed через override и возвращает 180.
  5. car.Describe() печатает Lada: 180 km/h: метод из Vehicle вызвал версию MaxSpeed из Car.

Абстрактный класс

Абстрактный класс — класс с модификатором abstract, объект которого создать нельзя. Строка var v = new Vehicle("Cart"); не скомпилируется:

TEXT
error CS0144: Cannot create an instance of the abstract type or interface 'Vehicle'

Зато переменная абстрактного типа разрешена: Vehicle car = new Car("Lada"); ведёт к объекту Car.

В остальном абстрактный класс устроен как обычный: в нём бывают поля, конструкторы и методы с телом. Конструктор Vehicle объявлен protected: вызывают его только конструкторы производных классов через : base(name), а больше и некому.

Абстрактный метод

Абстрактный метод — метод с модификатором abstract и без тела. Он объявляет: «Такой метод есть у каждого наследника», а тело оставляет наследникам. Для него действуют три правила:

  • абстрактный метод бывает только в абстрактном классе;
  • он виртуальный сам по себе: его переопределяют через override, хотя слова virtual у него нет;
  • неабстрактный производный класс обязан переопределить его, если этого ещё не сделал класс выше по цепочке наследования.

Второй вид транспорта — ещё один класс со своей версией MaxSpeed:

C#
Vehicle bike = new Bike("BMX");
Console.WriteLine(bike.Describe()); // BMX: 25 km/h

class Bike : Vehicle
{
    public Bike(string name) : base(name) { }
    public override int MaxSpeed() => 25;
}

Этот фрагмент продолжает первую программу: инструкции добавьте после её инструкций, а класс Bike — в конец файла. Метод Describe написан один раз, а работает для каждого вида со своей скоростью.

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

Здесь аналогия неточна. «Транспорта вообще» в автопарке не встретить, а переменная типа Vehicle в программе есть, и ведёт она всегда к объекту конкретного вида.

abstract или virtual

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

  • у virtual-метода есть тело, и производный класс может его переопределить, а может оставить как есть;
  • у abstract-метода тела нет, и неабстрактный производный класс не скомпилируется, пока метод не переопределён.

Частая ошибка

Объявили новый вид транспорта и забыли переопределить абстрактный метод:

C#
class Truck : Vehicle // ✗
{
    public Truck(string name) : base(name) { }
}
TEXT
error CS0534: 'Truck' does not implement inherited abstract member 'Vehicle.MaxSpeed()'

Программа не компилируется. У объекта Truck не было бы версии MaxSpeed, и вызову MaxSpeed() из Describe нечего было бы выполнить. Поэтому компилятор требует тело. Добавьте в Truck строку public override int MaxSpeed() => 90;.

Главное

  • Объект абстрактного класса создать нельзя (CS0144), но переменная его типа может вести к объекту производного класса.
  • У абстрактного метода нет тела; неабстрактный наследник не скомпилируется, пока метод не переопределён через override.
  • В абстрактном классе бывают поля, конструкторы и обычные методы с телом.
  • virtual-метод переопределить можно, а abstract-метод — нужно.