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

Последнее обновление: 29.12.2021

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

Кроме обычных классов в C# есть абстрактные классы. Зачем они нужны? Классы обычно представляют некий план определенного рода объектов или сущностей. Например, мы можем определить класс Car для преставления машин или класс Person для представления людей, вложив в эти классы соответствующие свойства, поля, методы, которые будут описывать данные объекты. Однако некоторые сущности, которые мы хотим выразить с помощью языка программирования, могут не иметь конкретного воплощения. Например, в реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами. И для описания подобных сущностей, которые не имеют конкретного воплощения, предназначены абстрактные классы.

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

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

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

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

Transport tesla = new Transport();

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

Transport car = new Car();
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("легковая машина едет");
	}
}

В данном случае класс Car не реализует абстрактный метод Move базового класса Transport и поэтому также определен как абстрактный. Однако любые неабстрактные классы, производные от Car, все равно должны реализовать все унаследованные абстрактные методы и свойства.

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

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

// абстрактный класс фигуры
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()}");
}	
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850