👨‍🎓️ Самоучитель по C#: абстрактные классы и члены классов

Осваиваем на наглядных примерах базовые концепции языка C# – абстрактные классы и члены классов.
👨‍🎓️ Самоучитель по C#: абстрактные классы и члены классов

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

Абстрактный класс похож на обычный класс. Он также может иметь переменные, методы, конструкторы, свойства. Единственное, что при определении абстрактных классов используется ключевое слово abstract.

Модификатор abstract:

  1. указывает, что реализация сущности с данным модификатором является неполной или отсутствует.
  2. может использоваться с классами, методами, свойствами, индексаторами и событиями.
  3. в объявлении класса указывает, что класс предназначен только для использования в качестве базового класса для других классов.

Члены, помеченные как абстрактные или включенные в абстрактный класс, должны быть реализованы с помощью классов, производных от абстрактных классов.

Например, определим абстрактный класс, который представляет некое транспортное средство:

        abstract class Transport
{
    public void Move()
    {
        Console.WriteLine("Транспортно средство движется");
    }
}
    

Транспортное средство представляет некоторую абстракцию, которая не имеет конкретного воплощения. Например, есть легковые и грузовые машины, самолеты, морские суда, но как такового транспортного средства нет. Тем не менее все транспортные средства имеют нечто общее – они могут перемещаться. И для этого в классе определен метод Move, который эмулирует перемещение.

Но главное отличие абстрактных классов от обычных состоит в том, что мы НЕ можем использовать конструктор абстрактного класса для создания экземпляра класса. Например, следующим образом:

        Transport tesla = new Transport();
    

Тем не менее абстрактные классы полезны для описания некоторого общего функционала, который могут наследовать и использовать производные классы:

        Transport ship = new Ship();
Transport aircraft = new Aircraft();
 
car.Move();
ship.Move();
aircraft.Move();
abstract class Transport
{
    public void Move()
    {
        Console.WriteLine("Транспортное средство движется");
    }
}
// класс корабля
class Ship : Transport { }
// класс самолета
class Aircraft : Transport { }
// класс машины
class Car : Transport { }
    

В данном случае от класса Transport наследуются три класса, которые представляют различные типы транспортных средств. Тем не менее они имеют общую черту – они могут перемещаться с помощью метода Move().

Мы не можем использовать конструктор абстрактного класса для создания экземпляра этого класса. Тем не менее такой класс также может определять конструкторы:

        Transport car = new Car("машина");
Transport ship = new Ship("корабль");
Transport aircraft = new Aircraft("самолет");
 
car.Move();         // машина движется
ship.Move();        // корабль движется
aircraft.Move();    // самолет движется
abstract class Transport
{
    public string Name { get; }
    // конструктор абстрактного класса Transport
    public Transport(string name)
    {
        Name = name;
    }
    public void Move() =>Console.WriteLine($"{Name} движется");
}
// класс корабля
class Ship : Transport 
{
    // вызываем конструктор базового класса
    public Ship(string name) : base(name) { }
}
// класс самолета
class Aircraft : Transport
{
    public Aircraft(string name) : base(name) { }
}
// класс машины
class Car : Transport
{
    public Car(string name) : base(name) { }
}
    

В данном случае в абстрактном классе Transport определен конструктор – с помощью параметра он устанавливает значение свойства Name, которое хранит название транспортного средства. И в этом случае производные классы должны в своих конструкторах вызвать этот конструктор.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека шарписта»

Абстрактные члены классов

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

  • Методы.
  • Свойства.
  • Индексаторы.
  • События.

Абстрактные члены классов не должны иметь модификатор private. При этом производный класс обязан переопределить и реализовать все абстрактные методы и свойства, которые имеются в базовом абстрактном классе. При переопределении в производном классе такой метод или свойство также объявляются с модификатором override (как и при обычном переопределении виртуальных методов и свойств). Также следует учесть, что если класс имеет хотя бы один абстрактный метод (или абстрактные свойство, индексатор, событие), то этот класс должен быть определен как абстрактный.

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

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

В примере с транспортными средствами метод Move описывает передвижение транспортного средства. Однако различные типы транспорта перемещаются по-разному – едут по земле, летят по воздуху, плывут по воде и т. д. В этом случае мы можем сделать метод Move абстрактным, а его реализацию переложить на производные классы:

        abstract class Transport
{
    public abstract void Move();
}
// класс корабля
class Ship : Transport 
{
    // мы должны реализовать все абстрактные методы и свойства базового класса
    public override void Move()
    {
        Console.WriteLine("Корабль плывет");
    }
}
// класс самолета
class Aircraft : Transport
{
    public override void Move()
    {
        Console.WriteLine("Самолет летит");
    }
}
// класс машины
class Car : Transport
{
    public override void Move()
    {
        Console.WriteLine("Машина едет");
    }
}


Transport car = new Car();
Transport ship = new Ship();
Transport aircraft = new Aircraft();
 
car.Move();         // машина едет
ship.Move();        // корабль плывет
aircraft.Move();    // самолет летит
    

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

Следует отметить использование абстрактных свойств. Их определение похоже на определение автосвойств. Например:

        abstract class Transport
{
    // абстрактное свойство для хранения скорости
    public abstract int Speed { get; set; } 
}
// класс корабля
class Ship: Transport
{
    int speed;
    public override int Speed 
    { 
        get => speed; 
        set => speed = value; 
    }
}
 
class Aircraft : Transport
{
    public override int Speed { get; set; }
}
    

В классе Transport определено абстрактное свойство Speed, которое должно хранить скорость транспортного средства. Оно похоже на автосвойство, но это не автосвойство. Так как данное свойство не должно иметь реализацию, то оно имеет только пустые блоки get и set. В производных классах мы можем переопределить это свойство, сделав его полноценным свойством (как в классе Ship), либо же сделав его автоматическим (как в классе Aircraft).

Отказ от реализации абстрактных членов

Производный класс обязан реализовать все абстрактные члены базового класса. Однако мы можем отказаться от реализации, но в этом случае производный класс также должен быть определен как абстрактный:

        Transport tesla = new Auto();
tesla.Move();           // легковая машина едет
abstract class Transport
{
    public abstract void Move();
}
// класс машины
abstract class Car :Transport{}
 
class Auto: Car
{
    public override void Move()
    {
        Console.WriteLine("легковая машина едет");
    }
}
    

Пример абстрактного класса

Примером является система геометрических фигур. В реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами:

        // абстрактный класс фигуры
abstract class Shape
{
    // абстрактный метод для получения периметра
    public abstract double GetPerimeter();
    // абстрактный метод для получения площади
    public abstract double GetArea();
}
// производный класс прямоугольника
class Rectangle : Shape
{
    public float Width { get; set; }
    public float Height { get; set; }
 
    // переопределение получения периметра
    public override double GetPerimeter() => Width * 2 + Height * 2;
    // переопрелеление получения площади
    public override double GetArea() => Width * Height;
}
// производный класс окружности
class Circle : Shape
{
    public double Radius { get; set; }
 
    // переопределение получения периметра
    public override double GetPerimeter() => Radius * 2 * 3.14;
    // переопрелеление получения площади
    public override double GetArea() => Radius * Radius * 3.14;
}
    
        var rectanle = new Rectangle { Width = 20, Height = 20 };
var circle = new Circle { Radius = 200 };
PrintShape(rectanle); // Perimeter: 80   Area: 400
PrintShape(circle); // Perimeter: 1256  Area: 125600
 
void PrintShape(Shape shape)
{
    Console.WriteLine($"Perimeter: {shape.GetPerimeter()}  Area: {shape.GetArea()}");
}
    
***

Материалы по теме

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
AppSec Business Partner
по итогам собеседования
C# Developer
Москва, по итогам собеседования
Backend Lead (Python, Django)
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ