Към съдържанието
wannacode

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

Проблем

Нула квадратни метра

Пишеш програма за плочки на тераса. Терасата е от три части: правоъгълник 4 × 3 м, кръгла част с радиус 1 м и триъгълник с основа 2 м и височина 3 м. Има родител Shape с Area(), който връща 0, а всяка фигура го презаписва.

Колегата, който е писал Triangle, е забравил:

class Triangle : Shape
{
public double Base { get; private set; }
public double Height { get; private set; }
public Triangle(double baseLength, double height)
{
Base = baseLength;
Height = height;
}
}
Shape[] terrace = { new Rectangle(4, 3), new Circle(1), new Triangle(2, 3) };
double total = 0;
foreach (Shape s in terrace)
{
total += s.Area();
}
Console.WriteLine($"Плочки: {total:F2} м²");
Изход
Плочки: 15.14 м²

Трябва 18,14 м². Програмата не дава грешка, а поръчката е с 3 м² по-малко. Нулата на родителя е скрила липсващия метод.

А какво е new Shape()? Фигура без форма. Няма смисъл да съществува такъв обект.

Идеята

Абстрактен клас: обещание, което наследниците изпълняват

Shape не е истинска фигура. Той казва само „всяка фигура има лице“. Затова го направи абстрактен:

abstract class Shape
{
public abstract double Area();
}
  • Абстрактният метод няма тяло, само заглавие и ;. Не можеш да дадеш разумно лице на „фигура изобщо“, затова не даваш никакво.
  • Абстрактният клас не може да има обекти: new Shape() не се компилира.
  • Всеки наследник трябва да напише метода. Иначе програмата не се компилира.

Сега забравеният Triangle дава грешка още при компилирането:

„'Triangle' does not implement inherited abstract member 'Shape.Area()'“. Наследникът пише метода с override, както при virtual.

Грешка, която компилаторът хваща, е по-добра от грешка в поръчката.
По-точно, за любопитните
  • Наследник, който не пише абстрактния метод, може да е абстрактен и той. Тогава обещанието минава към неговите наследници.
  • Клас може да е абстрактен и без нито един абстрактен метод. Така само забраняваш обектите от него.

Виж го

Триъгълникът изпълнява обещанието

Добави метода в Triangle:

class Triangle : Shape
{
// ... Base, Height и конструкторът
public override double Area()
{
return Base * Height / 2;
}
}
Shape[] terrace = { new Rectangle(4, 3), new Circle(1), new Triangle(2, 3) };
double total = 0;
foreach (Shape s in terrace)
{
total += s.Area();
}
Console.WriteLine($"Плочки: {total:F2} м²");
Изход
Плочки: 18.14 м²

Цикълът е същият. Масивът Shape[] пази фигури от три класа, а s.Area() вика метода на всяка. Полиморфизмът от миналия урок работи и с абстрактен родител.

Пробвай

Намери грешката

Само един ред не се компилира. Кой?

Избери реда с грешката.

Идеята

Абстрактен, но не празен

Абстрактният клас може да има всичко, което има обикновеният: полета, конструктор, обикновени методи. Нека всяка фигура има име и метод, който я описва:

abstract class Shape
{
public string Name { get; private set; }
public Shape(string name)
{
Name = name;
}
public abstract double Area();
public string Describe()
{
return $"{Name}: {Area():F2} м²";
}
}

Describe() е написан веднъж, в Shape. Той вика Area(), който в Shape още го няма. Не е проблем: Describe() винаги се вика върху истинска фигура, а тя има лице.

От Shape не се правят обекти, но конструкторът му е полезен: наследниците го викат.

class Circle : Shape
{
public double Radius { get; private set; }
public Circle(double radius) : base("кръг")
{
Radius = radius;
}
// ... Area() както преди
}
Shape[] terrace = { new Rectangle(4, 3), new Circle(1), new Triangle(2, 3) };
foreach (Shape s in terrace)
{
Console.WriteLine(s.Describe());
}
Изход
правоъгълник: 12.00 м²
кръг: 3.14 м²
триъгълник: 3.00 м²

Родителят пише общото (името, описанието), а наследниците пишат различното (лицето).

public abstract double Area();
public string Describe()
{
return $"{Name}: {Area():F2} м²";
}
Shape s = new Circle(1);
Console.WriteLine(s.Describe());
Стъпка по стъпка, с думи
  1. Shape обещава Area(), но няма тяло. Обект само от Shape не може да има.
  2. Обектът е кръг: частта Circle има свой Area(). Тя изпълнява обещанието.
  3. s.Describe() е написан в Shape и важи за всяка фигура.
  4. Той вика Area(). Обектът е кръг, затова се изпълнява методът на Circle: π · 1 · 1.
  5. Describe() сглобява текста. Общото е в родителя, различното в наследника.
  6. Изходът е лицето на кръга, с два знака след запетаята.

Пробвай

Чий метод?

Какво ще отпечата програмата?

// Shape и Circle са от горната карта
Shape s = new Circle(2);
Console.WriteLine(s.Describe());
Отговор

Пробвай

Кое липсва?

Какво трябва да пише на празното място, за да не може да се създаде обект от Shape?

 class Shape
{
    public abstract double Area();
}

Упражнение

Фигури и животни

Реши задачите в редактора си.

1. Лице и обиколка. Абстрактен клас Shape с име и два абстрактни метода: Area() и Perimeter(). Наследници Circle (радиус) и Rectangle (ширина и височина). Сложи кръг с радиус 5 и правоъгълник 4 × 2,5 в масив и отпечатай двете стойности с два знака след точката.

Изход
Кръг: лице 78.54, обиколка 31.42
Правоъгълник: лице 10.00, обиколка 13.00

2. Животните. Абстрактен клас Animal с абстрактен метод Sound() и обикновен метод Breathe(), който печата „Животното диша.“ Наследници Cat и Dog. Извикай звука на двете в цикъл, после Breathe() на първото.

Изход
Мяу, мяу!
Бау, бау!
Животното диша.

Упражнение

Превозни средства и изпълнения

3. Превозни средства. Абстрактен клас Vehicle с абстрактен метод Move(). SimpleCar и Bicycle го наследяват. Bicycle има и свой метод Pedal().

Изход
Колата потегля
Велосипедът потегля
Въртя педалите
Опитай garage[1].Pedal(). Защо не се компилира, въпреки че там има велосипед?

4. Изпълнения. Абстрактен клас Performance със заглавие и абстрактен метод Start(). DanceMove и Song го наследяват. Song има и метод Sing().

Изход
Започва танцът moonwalk
Започва песента 'Bohemian Rhapsody'
Пея 'Bohemian Rhapsody'

Още задачи

  • Триъгълник. Добави Triangle към задача 1 с трите страни. Обиколката е сумата им, а лицето се смята по формулата на Херон: s = обиколка / 2, лице = √(s(s − a)(s − b)(s − c)). За страни 3, 4 и 5 лицето е 6,00, а обиколката 12,00.
  • Абстрактен служител. Направи Employee от миналия урок абстрактен, с абстрактен MonthlyPay(). Добави FullTime (заплатата е цялата) и остави Manager и Intern. Какво печели програмата? Какво губи?

Тест

Провери се

1. Shape е абстрактен клас. Кое се компилира?
2. Какво няма абстрактният метод?
3. Triangle наследява абстрактния Shape, но не пише Area(). Какво става?
4. Може ли абстрактен клас да има полета, конструктор и обикновени методи?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·