TypeScript: абстрактные классы
Абстрактные классы — важная концепция объектно-ориентированного программирования на TypeScript.
Это класс, экземпляр которого нельзя создать напрямую, и он может служить только базовым классом для наследования другими классами.
Абстрактные классы в основном используются для определения общих свойств и методов для подклассов, предоставляя подклассам единый шаблон структуры и поведения.
SVG-диаграмма: иерархия наследования абстрактных классов
Зачем нужны абстрактные классы
В объектно-ориентированном программировании нам часто нужно определять базовые классы.
Эти базовые классы описывают общие свойства и методы для подклассов, но детали реализации некоторых методов должны определяться самими подклассами.
Абстрактные классы используются для решения этой проблемы: они позволяют нам определять сигнатуры методов (объявления), оставляя конкретную реализацию подклассам.
Объяснение концепции:Абстрактный класс — это тип между обычным классом и интерфейсом. Он обладает как характеристиками интерфейса (определение сигнатур методов), так и характеристиками класса (может иметь конкретные реализации методов и конструкторы).
Основы абстрактных классов
Используйтеabstractключевое слово `abstract` для объявления абстрактных классов и абстрактных методов.
Абстрактные классы могут содержать абстрактные методы и конкретные методы (методы с существующей реализацией).
Пример
// Абстрактный класс нельзя напрямую создать экземпляр; он может служить только базовым классом
abstract class Animal {
// Свойство имени Animal
name: string;
// Конструктор
constructor(name: string) {
this.name = name;
}
// Абстрактный метод: модифицируется с помощью abstract, без тела метода
// Подклассы должны реализовать этот метод
abstract speak(): void;
// Конкретный метод: метод с конкретной реализацией
// Подклассы могут напрямую наследовать и использовать его без переопределения
move(): void {
console.log(this.name + " движется");
}
}
// Попытка создать экземпляр абстрактного класса вызовет ошибку
// var animal = new Animal("Animal"); // Ошибка: невозможно создать экземпляр абстрактного класса
// Определите класс Dog, который наследуется от Animal
class Dog extends Animal {
// Подкласс должен реализовать абстрактный метод speak() родительского класса
speak(): void {
console.log(this.name + " Woof woof!");
}
}
// Создать экземпляр Dog
var dog = new Dog("Wangcai");
dog.speak(); // Вызов метода, реализованного подклассом
dog.move(); // Наследование конкретного метода от родительского класса
Вывод:
Wangcai, woof woof woof! Wangcai is moving.
Объяснение:Абстрактные методы не имеют тела метода (только сигнатуру метода), и подклассы должны их реализовывать. Конкретные методы имеют конкретные реализации, и подклассы могут напрямую наследовать и использовать их.
Абстрактные методы
Абстрактные методы — это методы, объявленные, но не реализованные в абстрактном классе.
После того как подкласс наследует абстрактный класс, он должен реализовать все абстрактные методы; иначе возникнет ошибка.
Пример
abstract class Shape {
// Абстрактный метод: только объявление, без реализации
// Подклассы должны реализовать эти два метода
abstract area(): number;
abstract perimeter(): number;
// Конкретный метод: может использовать абстрактные методы
// Этот метод вызывает абстрактный метод; подклассы могут использовать его как обычно после наследования
describe(): void {
console.log("Площадь: " + this.area().toFixed(2));
}
}
// Определить класс прямоугольника, который наследует Shape
class Rectangle extends Shape {
// Ширина Rectangle
width: number;
// Высота Rectangle
height: number;
// Конструктор
constructor(width: number, height: number) {
super(); // Вызвать конструктор родительского класса
this.width = width;
this.height = height;
}
// Реализовать абстрактный метод: вычислить площадь
area(): number {
return this.width * this.height;
}
// Реализовать абстрактный метод: вычислить периметр
perimeter(): number {
return 2 * (this.width + this.height);
}
}
// Создать экземпляр прямоугольника
var rect = new Rectangle(4, 5);
// Вызвать унаследованный метод describe, который внутри вызывает метод area подкласса
rect.describe();
console.log("Периметр: " + rect.perimeter());
Вывод:
面积: 20.00 周长: 18
Совет:Конкретные методы могут вызывать абстрактные методы. Это потому, что когда вызывается конкретный метод, подкласс уже реализовал абстрактный метод, поэтому он может выполняться нормально.
Абстрактный класс как тип
Абстрактный класс можно использовать как тип параметра.
Это означает, что функция может принимать экземпляры любого подкласса абстрактного класса, обеспечивая полиморфизм.
Пример
abstract class Animal {
// Абстрактный метод: все животные издают звуки
abstract speak(): void;
}
// Определить класс Cat, наследующий Animal
class Cat extends Animal {
// Реализовать абстрактный метод
speak(): void {
console.log("Мяу мяу мяу!");
}
}
// Определить класс Dog, наследующий Animal
class Dog extends Animal {
// Реализовать абстрактный метод
speak(): void {
console.log("Гав гав гав!");
}
}
// Определяем тип параметра функции как абстрактный класс Animal
// Эта функция может принимать любой экземпляр подкласса Animal
function makeSpeak(animal: Animal): void {
animal.speak();
}
// Передача экземпляров разных подклассов приводит к разному поведению (полиморфизм)
makeSpeak(new Cat());
makeSpeak(new Dog());
// Массив типа абстрактного класса: может хранить экземпляры разных подклассов
var animals: Animal[] = [new Cat(), new Dog()];
Вывод:
Meow meow meow! Woof woof woof!
Полиморфизм:Когда абстрактный класс используется как тип, фактически выполняется реализация метода подкласса. Это особенность полиморфизма объектно-ориентированного программирования.
Различия между абстрактными классами и интерфейсами
И абстрактные классы, и интерфейсы используются для определения спецификаций типов, но между ними есть некоторые ключевые различия.
Понимание их различий помогает сделать правильный выбор в реальной разработке.
| Характеристика | Абстрактный класс | Интерфейс |
|---|---|---|
| Создание экземпляра | Нельзя создать экземпляр напрямую | Нельзя создать экземпляр напрямую |
| Реализация методов | Может иметь конкретные реализации | Может иметь только объявления (в TypeScript 3.6+ могут быть реализации по умолчанию) |
| Модификаторы членов | Можно добавлять public, protected, private | Может иметь только readonly |
| Наследование | Одиночное наследование (можно расширять только один класс) | Множественная реализация (можно реализовать несколько интерфейсов) |
| Конструктор | Может иметь конструктор | Не может иметь конструктор |
Рекомендации по выбору:Используйте абстрактные классы, когда нужен общий код; используйте интерфейсы, когда нужно определить спецификации/контракты; используйте интерфейсы, когда необходимо множественное наследование.
Полный пример: платёжная система
Ниже приведён пример платёжной системы, реализованной с использованием абстрактного класса.
Этот пример демонстрирует применение абстрактных классов в реальных проектах.
Пример
abstract class Payment {
// Абстрактный метод: обработать платёж, подклассы должны его реализовать
abstract process(amount: number): boolean;
// Конкретный метод: проверить информацию о платеже
// Все подклассы могут использовать этот метод
validate(): void {
console.log("Проверить информацию о платеже");
}
}
// Класс платежа кредитной картой
class CreditCardPayment extends Payment {
// Номер кредитной карты
cardNumber: string;
// Конструктор
constructor(cardNumber: string) {
super(); // Вызвать конструктор родительского класса
this.cardNumber = cardNumber;
}
// Абстрактный метод: обработка платежа кредитной картой
process(amount: number): boolean {
console.log("Обработка платежа кредитной картой: " + amount);
return true;
}
}
// Класс платежа PayPal
class PayPalPayment extends Payment {
// Электронная почта PayPal
email: string;
// Конструктор
constructor(email: string) {
super();
this.email = email;
}
// Реализовать абстрактный метод: обработать платеж PayPal
process(amount: number): boolean {
console.log("Обработка платежа PayPal: " + amount);
return true;
}
}
// Использовать полиморфизм для обработки различных методов оплаты
var payments: Payment[] = [
new CreditCardPayment("1234"),
new PayPalPayment("[email protected]")
];
// Перебрать и обработать каждый метод оплаты
for (var _i = 0, payments_1 = payments; _i < payments_1.length; _i++) {
var payment = payments_1[_i];
// Вызов унаследованного метода проверки
payment.validate();
// Вызов метода обработки подкласса
payment.process(100);
}
Вывод:
验证支付信息 处理信用卡支付: 100 验证支付信息 处理 PayPal 支付: 100
Сценарии применения:Абстрактные классы часто используются при проектировании фреймворков и разделении бизнес-логики на слои. Например, обработка платежей, аутентификация пользователей, сохранение данных и другие сценарии.
Примечания
- Нельзя создать экземпляр:Абстрактные классы нельзя напрямую создать с помощью new; от них должны наследоваться подклассы
- Абстрактные методы должны быть реализованы:Возникает ошибка, если подкласс не реализует все абстрактные методы
- Могут иметь конструкторы:Абстрактные классы могут определять конструкторы, а подклассам нужно вызывать super()
- Одиночное наследование:Класс может наследовать только один абстрактный класс (одиночное наследование)
- Модификаторы доступа:Абстрактные методы могут использовать модификаторы public и protected
Лучшие практики:Используйте абстрактные классы, когда нескольким классам нужно совместно использовать код и логику. Используйте интерфейсы, когда нужно определить только спецификации и контракты.
Итог
Абстрактные классы — важная часть объектно-ориентированного программирования на TypeScript.
- Ключевое слово abstract:Используется для объявления абстрактных классов и методов
- Нельзя создать экземпляр:Может использоваться только через наследование подклассами
- Абстрактные методы:Методы, которые должны реализовать подклассы
- Конкретные методы:Реализованные методы, которые подклассы могут напрямую наследовать и использовать
- Паттерн «шаблонный метод»:Абстрактные классы определяют каркас, подклассы предоставляют конкретные реализации
Другие расширенияРекомендация:При проектировании иерархий классов отдавайте предпочтение использованию абстрактных классов для совместного использования кода и определения спецификаций.