Abstract classes and abstract methods
Basic Concepts of Abstract Classes and Abstract Methods
An abstract class is a special type of class that cannot be instantiated and can only be inherited. Abstract classes may contain abstract methods, which have declarations but no implementations and must be concretely implemented in subclasses. The primary purpose of abstract classes is to define a set of common interfaces and behavioral specifications, enforcing subclasses to adhere to a specific structure.
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Moving...");
}
}
Characteristics of Abstract Classes
Abstract classes have several key differences from regular classes:
- Abstract classes must be declared using the
abstract
keyword - Abstract classes cannot be directly instantiated
- Abstract classes may contain both abstract methods and concrete methods
- Abstract classes can have constructors
- Abstract classes may contain member variables
abstract class Department {
constructor(public name: string) {}
abstract printMeeting(): void;
printName(): void {
console.log("Department name: " + this.name);
}
}
Features of Abstract Methods
Abstract methods are the core concept of abstract classes and have the following characteristics:
- Must be marked with the
abstract
keyword - Cannot have concrete implementations (no method body)
- Must be implemented in derived classes
- May include parameter and return type declarations
- Access modifiers can be public, protected, or private
abstract class Shape {
abstract getArea(): number;
abstract getPerimeter(): number;
displayInfo(): void {
console.log(`Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`);
}
}
Implementing Subclasses of Abstract Classes
When inheriting from an abstract class, subclasses must implement all abstract methods; otherwise, the subclass must also be declared as abstract.
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
const circle = new Circle(5);
circle.displayInfo(); // Output: Area: 78.53981633974483, Perimeter: 31.41592653589793
Differences Between Abstract Classes and Interfaces
Although abstract classes and interfaces are similar in some aspects, they have important differences:
Feature | Abstract Class | Interface |
---|---|---|
Implementation | Can contain concrete implementations | Only declarations |
Member Variables | Allowed | Not allowed |
Constructors | Allowed | Not allowed |
Multiple Inheritance | Not supported | Supported |
Access Modifiers | Allowed | Defaults to public |
// Interface example
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
// Abstract class example
abstract class ClockAbstract {
abstract currentTime: Date;
abstract setTime(d: Date): void;
getTime(): Date {
return this.currentTime;
}
}
Practical Use Cases for Abstract Classes
Abstract classes are particularly suitable for the following scenarios:
- Defining the foundational structure of frameworks or libraries
- Enforcing derived classes to implement specific methods
- Providing partial common implementations
- Creating families of classes with shared behaviors
abstract class DataAccess<T> {
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;
abstract query(sql: string): Promise<T[]>;
async executeTransaction(queries: string[]): Promise<void> {
try {
await this.connect();
for (const query of queries) {
await this.query(query);
}
} finally {
await this.disconnect();
}
}
}
class MySQLAccess extends DataAccess<any> {
async connect(): Promise<void> { /* MySQL connection implementation */ }
async disconnect(): Promise<void> { /* MySQL disconnection implementation */ }
async query(sql: string): Promise<any[]> { /* MySQL query implementation */ }
}
Constructors in Abstract Classes
Abstract classes can have constructors. Although abstract classes cannot be directly instantiated, subclasses can invoke the parent class's constructor via super()
.
abstract class Person {
constructor(protected name: string, protected age: number) {}
abstract introduce(): string;
}
class Employee extends Person {
constructor(name: string, age: number, private jobTitle: string) {
super(name, age);
}
introduce(): string {
return `Hi, I'm ${this.name}, ${this.age} years old, working as ${this.jobTitle}`;
}
}
const emp = new Employee("Alice", 30, "Developer");
console.log(emp.introduce());
Abstract Properties
In addition to abstract methods, TypeScript also supports abstract properties, which must be implemented in derived classes.
abstract class Vehicle {
abstract brand: string;
abstract model: string;
abstract year: number;
getFullDescription(): string {
return `${this.year} ${this.brand} ${this.model}`;
}
}
class Car extends Vehicle {
brand = "Toyota";
model = "Camry";
year = 2020;
}
const myCar = new Car();
console.log(myCar.getFullDescription()); // Output: 2020 Toyota Camry
Abstract Classes and Polymorphism
Abstract classes are an important way to implement polymorphism. Through interfaces defined by abstract classes, the specific implementation to be called can be determined at runtime.
abstract class PaymentProcessor {
abstract processPayment(amount: number): boolean;
}
class CreditCardProcessor extends PaymentProcessor {
processPayment(amount: number): boolean {
console.log(`Processing credit card payment of $${amount}`);
return true;
}
}
class PayPalProcessor extends PaymentProcessor {
processPayment(amount: number): boolean {
console.log(`Processing PayPal payment of $${amount}`);
return true;
}
}
function handlePayment(processor: PaymentProcessor, amount: number) {
processor.processPayment(amount);
}
const creditCard = new CreditCardProcessor();
const paypal = new PayPalProcessor();
handlePayment(creditCard, 100); // Processing credit card payment of $100
handlePayment(paypal, 50); // Processing PayPal payment of $50
Abstract Classes and Design Patterns
Abstract classes play important roles in many design patterns, such as the Template Method pattern and Factory Method pattern.
// Template Method pattern example
abstract class DataExporter {
// Template method
export(): void {
this.prepareData();
this.validateData();
this.sendData();
this.cleanup();
}
protected abstract prepareData(): void;
protected abstract validateData(): void;
protected sendData(): void {
console.log("Sending data to server...");
}
protected cleanup(): void {
console.log("Cleaning up temporary files...");
}
}
class CSVExporter extends DataExporter {
protected prepareData(): void {
console.log("Preparing CSV data...");
}
protected validateData(): void {
console.log("Validating CSV data...");
}
}
const exporter = new CSVExporter();
exporter.export();
Limitations of Abstract Classes
Although abstract classes are powerful, they also have limitations:
- TypeScript is a single-inheritance language; a class can only inherit from one abstract class
- Abstract classes add hierarchical relationships, which may complicate code structure
- Overuse of abstract classes may lead to over-engineering
// Single inheritance limitation example
abstract class A {
abstract methodA(): void;
}
abstract class B {
abstract methodB(): void;
}
// Error: Cannot inherit from both A and B
class C extends A /*, B*/ {
methodA(): void { /* Implementation */ }
// methodB(): void { /* Cannot implement simultaneously */ }
}
Abstract Classes and Mixins
Although TypeScript doesn't support multiple inheritance, the mixin pattern can be used to simulate inheriting behaviors from multiple abstract classes.
// Mixin pattern example
class Disposable {
isDisposed: boolean = false;
dispose() { this.isDisposed = true; }
}
class Activatable {
isActive: boolean = false;
activate() { this.isActive = true; }
deactivate() { this.isActive = false; }
}
// Using mixins
class SmartObject implements Disposable, Activatable {
constructor() {
setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
}
// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
Abstract Classes and Dependency Injection
Abstract classes are often used in dependency injection scenarios to define service contracts.
abstract class LoggerService {
abstract log(message: string): void;
abstract error(message: string): void;
abstract warn(message: string): void;
}
class ConsoleLogger extends LoggerService {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
error(message: string): void {
console.error(`[ERROR] ${message}`);
}
warn(message: string): void {
console.warn(`[WARN] ${message}`);
}
}
class App {
constructor(private logger: LoggerService) {}
run() {
this.logger.log("Application started");
this.logger.warn("This is a warning");
this.logger.error("This is an error");
}
}
const app = new App(new ConsoleLogger());
app.run();
Abstract Classes and Access Modifiers
Members in abstract classes can have different access levels, controlling access permissions for derived classes and external code.
abstract class AccessExample {
public abstract publicMethod(): void;
protected abstract protectedMethod(): void;
private privateMethod(): void {
console.log("This is private");
}
// Abstract private methods are supported in TypeScript 4.3+
// private abstract privateAbstractMethod(): void;
}
class AccessImpl extends AccessExample {
public publicMethod(): void {
console.log("Public method implementation");
}
protected protectedMethod(): void {
console.log("Protected method implementation");
// this.privateMethod(); // Error: Cannot access private method
}
// Cannot implement parent class's private abstract method
// private privateAbstractMethod(): void {}
}
const instance = new AccessImpl();
instance.publicMethod();
// instance.protectedMethod(); // Error: Protected method can only be accessed within the class or its subclasses
Abstract Classes and Static Members
Abstract classes can contain static members, which belong to the class itself rather than instances.
abstract class MathOperations {
static PI: number = 3.14159;
static calculateCircleArea(radius: number): number {
return this.PI * radius * radius;
}
abstract calculate(): number;
}
class Square extends MathOperations {
constructor(private side: number) {
super();
}
calculate(): number {
return this.side * this.side;
}
}
console.log(MathOperations.calculateCircleArea(5)); // 78.53975
const square = new Square(4);
console.log(square.calculate()); // 16
Abstract Classes and Generics
Abstract classes can be combined with generics to create more flexible type structures.
abstract class Repository<T> {
abstract getAll(): T[];
abstract getById(id: number): T | undefined;
abstract add(item: T): void;
abstract update(item: T): boolean;
abstract delete(id: number): boolean;
}
class UserRepository extends Repository<User> {
private users: User[] = [];
getAll(): User[] {
return [...this.users];
}
getById(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
add(user: User): void {
this.users.push(user);
}
update(user: User): boolean {
const index = this.users.findIndex(u => u.id === user.id);
if (index >= 0) {
this.users[index] = user;
return true;
}
return false;
}
delete(id: number): boolean {
const initialLength = this.users.length;
this.users = this.users.filter(u => u.id !== id);
return this.users.length !== initialLength;
}
}
interface User {
id: number;
name: string;
email: string;
}
Abstract Classes and Decorators
Abstract classes can be used with decorators to enhance class functionality.
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
abstract class SealedAbstract {
abstract method(): void;
concreteMethod() {
console.log("Concrete implementation");
}
}
// Attempting to modify SealedAbstract will fail
// SealedAbstract.prototype.newMethod = function() {}; // Error: Cannot add property
Abstract Classes and Conditional Types
In advanced type scenarios, abstract classes can be combined with conditional types.
abstract class ShapeBase {
abstract area(): number;
}
class Circle extends ShapeBase {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
}
class Square extends ShapeBase {
constructor(private side: number) { super(); }
area(): number { return this.side ** 2; }
}
type ShapeType<T extends ShapeBase> = T extends Circle ? "circle" : "square";
function getShapeType<T extends ShapeBase>(shape: T): ShapeType<T> {
return shape instanceof Circle ? "circle" : "square" as ShapeType<T>;
}
const circle = new Circle(5);
const square = new Square(4);
console.log(getShapeType(circle)); // "circle"
console.log(getShapeType(square)); // "square"
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn