Абстрактни класове без abstract
Проблем
Нула квадратни метра
Пишеш програма за плочки на тераса. Терасата е от три части: правоъгълник 4 × 3 м, кръгла част с радиус 1 м и триъгълник с основа 2 м и височина 3 м. Има родител Shape с area(), който връща 0, а всяка фигура го презаписва.
Колегата, който е писал Triangle, е забравил:
class Triangle extends Shape { #base; #height;
constructor(base, height) { super(); this.#base = base; this.#height = height; }}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];let total = 0;for (const s of terrace) { total += s.area();}console.log(`Плочки: ${total.toFixed(2)} м²`);Плочки: 15.14 м²Трябва 18,14 м². Програмата не дава грешка, а поръчката е с 3 м² по-малко. Нулата на родителя е скрила липсващия метод.
А какво е new Shape()? Фигура без форма. Няма смисъл да съществува такъв обект.
Идеята
Абстрактен клас: обещание, което наследниците изпълняват
Shape не е истинска фигура. Той казва само „всяка фигура има лице“. Затова го направи абстрактен:
class Shape { constructor() { if (new.target === Shape) { throw new Error("Shape е абстрактен: създай конкретна фигура"); } }
area() { throw new Error("area() трябва да се напише в наследника"); }}JavaScript няма abstract. Родителят сам пази правилото, с две проверки:
- „Абстрактният“ метод е метод на родителя, който не връща нищо разумно, а хвърля грешка:
throw new Error(...)спира програмата и казва защо. Подробно заthrowе в модул „Грешки и изключения“. - „Абстрактният“ клас проверява в конструктора си
new.target: класа, чийто обект се създава. Приnew Shape()той еShape, и конструкторът хвърля грешка. Приnew Circle(1)той еCircle, и проверката пропуска. - Затова наследниците викат
super()в конструктора си, дори да нямат какво да подадат.
Нищо от това не се проверява предварително. Забравеният Triangle се създава спокойно. Но когато цикълът стигне до неговото area(), програмата спира с „Error: area() трябва да се напише в наследника“, вместо тихо да сметне 3 м² по-малко.
По-точно, за любопитните
new.targetработи само в конструктор. Извън него еundefined.- TypeScript, JavaScript с типове, има истински
abstractи го проверява, преди програмата да тръгне. В самия JavaScript такъв няма.
Виж го
Триъгълникът изпълнява обещанието
Добави метода в Triangle:
class Triangle extends Shape { // ... #base, #height и конструкторът
area() { return this.#base * this.#height / 2; }}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];let total = 0;for (const s of terrace) { total += s.area();}console.log(`Плочки: ${total.toFixed(2)} м²`);Плочки: 18.14 м²Цикълът е същият. Масивът пази фигури от три класа, а s.area() вика метода на всяка. Полиморфизмът от миналия урок работи и с абстрактен родител.
Пробвай
Намери грешката
Избери реда с грешката.
new Shape() е опит да се създаде обект от абстрактен клас. Първият ред е наред: new Array(3) създава масив от три празни места за фигури, а не фигури. В тях можеш да сложиш правоъгълник, кръг или триъгълник. Конструкторът на Shape вижда, че new.target е Shape, и спира програмата с „Shape е абстрактен: създай конкретна фигура“.
Идеята
Абстрактен, но не празен
Абстрактният клас може да има всичко, което има обикновеният: полета, конструктор, обикновени методи. Нека всяка фигура има име и метод, който я описва:
class Shape { #name;
constructor(name) { if (new.target === Shape) { throw new Error("Shape е абстрактен: създай конкретна фигура"); } this.#name = name; }
area() { throw new Error("area() трябва да се напише в наследника"); }
describe() { return `${this.#name}: ${this.area().toFixed(2)} м²`; }}describe() е написан веднъж, в Shape. Той вика area(), който в Shape само хвърля грешка. Не е проблем: describe() винаги се вика върху истинска фигура, а тя има лице.
От Shape не се правят обекти, но конструкторът му е полезен: наследниците го викат.
class Circle extends Shape { #radius;
constructor(radius) { super("кръг"); this.#radius = radius; }
// ... area() както преди}const terrace = [new Rectangle(4, 3), new Circle(1), new Triangle(2, 3)];for (const s of terrace) { console.log(s.describe());}правоъгълник: 12.00 м²кръг: 3.14 м²триъгълник: 3.00 м²Родителят пише общото (името, описанието), а наследниците пишат различното (лицето).
area() { throw new Error("area() трябва да се напише в наследника");}
describe() { return `${this.#name}: ${this.area().toFixed(2)} м²`;}
const s = new Circle(1);console.log(s.describe());Стъпка по стъпка, с думи
- Shape „обещава“ area(): тялото само хвърля грешка. Обект само от Shape не може да има.
- Обектът е кръг: частта Circle има свой area(). Тя изпълнява обещанието.
- s.describe() е написан в Shape и важи за всяка фигура.
- Той вика area(). Обектът е кръг, затова се изпълнява методът на Circle: π · 1 · 1.
- describe() сглобява текста. Общото е в родителя, различното в наследника.
- Изходът е лицето на кръга, с два знака след запетаята.
Пробвай
Чий метод?
Какво ще отпечата програмата?
// Shape и Circle са от горната карта
const s = new Circle(2);console.log(s.describe());Обектът е кръг с радиус 2. describe() е на Shape, но area() в него е на кръга: π · 2 · 2 ≈ 12,57.
Пробвай
Кое липсва?
Какво трябва да пише на празното място, за да не може да се създаде обект от Shape?
class Shape {
constructor() {
if ( === Shape) {
throw new Error("Shape е абстрактен: създай конкретна фигура");
}
}
area() {
throw new Error("area() трябва да се напише в наследника");
}
}new.target: класът, чийто обект се създава. Само при new Shape() той е Shape, затова наследниците минават.Упражнение
Фигури и животни
Реши задачите в редактора си.
1. Лице и обиколка. Абстрактен клас Shape с име и два абстрактни метода: area() и perimeter(). Наследници Circle (радиус) и Rectangle (ширина и височина). Сложи кръг с радиус 5 и правоъгълник 4 × 2,5 в масив и отпечатай двете стойности с два знака след точката.
Кръг: лице 78.54, обиколка 31.42Правоъгълник: лице 10.00, обиколка 13.002. Животните. Абстрактен клас Animal с абстрактен метод sound() и обикновен метод breathe(), който печата „Животното диша.“ Наследници Cat и Dog. Извикай звука на двете в цикъл, после breathe() на първото.
Мяу, мяу!Бау, бау!Животното диша.Упражнение
Превозни средства и изпълнения
3. Превозни средства. Абстрактен клас Vehicle с абстрактен метод move(). SimpleCar и Bicycle го наследяват. Bicycle има и свой метод pedal().
Колата потегляВелосипедът потегляВъртя педалитеgarage[1].pedal(): работи, защото там има велосипед. А garage[0].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. Какво печели програмата? Какво губи?
Тест