TypeScript: абстрактные классы

Абстрактные классы — важная концепция объектно-ориентированного программирования на TypeScript.

Это класс, экземпляр которого нельзя создать напрямую, и он может служить только базовым классом для наследования другими классами.

Абстрактные классы в основном используются для определения общих свойств и методов для подклассов, предоставляя подклассам единый шаблон структуры и поведения.


SVG-диаграмма: иерархия наследования абстрактных классов Background Title Abstract Class Inheritance Hierarchy Structure Abstract class Animal abstract class Animal Cannot be instantiated directly Arrows to subclasses Subclass Dog class Dog extends Animal Subclass Cat class Cat extends Animal Abstract method description Abstract class features Feature list abstract method: subclasses must implement it Concrete method: subclasses can use it directly Can be used as a type: accepts subclass instances Arrow marker

Зачем нужны абстрактные классы

В объектно-ориентированном программировании нам часто нужно определять базовые классы.

Эти базовые классы описывают общие свойства и методы для подклассов, но детали реализации некоторых методов должны определяться самими подклассами.

Абстрактные классы используются для решения этой проблемы: они позволяют нам определять сигнатуры методов (объявления), оставляя конкретную реализацию подклассам.

Объяснение концепции:Абстрактный класс — это тип между обычным классом и интерфейсом. Он обладает как характеристиками интерфейса (определение сигнатур методов), так и характеристиками класса (может иметь конкретные реализации методов и конструкторы).


Основы абстрактных классов

Используйтеabstractключевое слово `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.

Объяснение:Абстрактные методы не имеют тела метода (только сигнатуру метода), и подклассы должны их реализовывать. Конкретные методы имеют конкретные реализации, и подклассы могут напрямую наследовать и использовать их.


Абстрактные методы

Абстрактные методы — это методы, объявленные, но не реализованные в абстрактном классе.

После того как подкласс наследует абстрактный класс, он должен реализовать все абстрактные методы; иначе возникнет ошибка.

Пример

// Определить абстрактный класс Shape
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

Совет:Конкретные методы могут вызывать абстрактные методы. Это потому, что когда вызывается конкретный метод, подкласс уже реализовал абстрактный метод, поэтому он может выполняться нормально.


Абстрактный класс как тип

Абстрактный класс можно использовать как тип параметра.

Это означает, что функция может принимать экземпляры любого подкласса абстрактного класса, обеспечивая полиморфизм.

Пример

// Определить абстрактный класс Animal
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:Используется для объявления абстрактных классов и методов
  • Нельзя создать экземпляр:Может использоваться только через наследование подклассами
  • Абстрактные методы:Методы, которые должны реализовать подклассы
  • Конкретные методы:Реализованные методы, которые подклассы могут напрямую наследовать и использовать
  • Паттерн «шаблонный метод»:Абстрактные классы определяют каркас, подклассы предоставляют конкретные реализации

Рекомендация:При проектировании иерархий классов отдавайте предпочтение использованию абстрактных классов для совместного использования кода и определения спецификаций.

Другие расширения