👨🎓️ Самоучитель по C#: абстрактные классы и члены классов
Осваиваем на наглядных примерах базовые концепции языка C# – абстрактные классы и члены классов.
Абстрактные классы
Абстрактный класс похож на обычный класс. Он также может иметь переменные, методы, конструкторы, свойства. Единственное, что при определении абстрактных классов используется ключевое слово abstract
.
Модификатор abstract
:
- указывает, что реализация сущности с данным модификатором является неполной или отсутствует.
- может использоваться с классами, методами, свойствами, индексаторами и событиями.
- в объявлении класса указывает, что класс предназначен только для использования в качестве базового класса для других классов.
Члены, помеченные как абстрактные или включенные в абстрактный класс, должны быть реализованы с помощью классов, производных от абстрактных классов.
Например, определим абстрактный класс, который представляет некое транспортное средство:
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()}"); }