如何在TypeScript/JavaScript中优雅抽象并复用单例模式?
优雅实现TypeScript/JavaScript多类单例抽象
针对你的问题,这里提供两种优雅的方案,既能抽象复用单例逻辑,又能强制新增类实现单例:
方案一:抽象单例基类(TypeScript优先)
通过定义抽象基类封装单例逻辑,子类继承后自动获得单例能力,同时限制直接实例化:
abstract class Singleton<T> { private static _instance: any; // 用protected代替private,允许子类继承构造函数 protected constructor() { // 防止通过new直接实例化子类 if ((this.constructor as typeof Singleton)._instance) { throw new Error('请通过getInstance()获取单例实例'); } } // 静态方法,子类继承后自动拥有,每个子类维护独立的实例 public static getInstance<T>(): T { if (!this._instance) { this._instance = new this(); } return this._instance as T; } }
子类实现示例
class Car extends Singleton<Car> { private _name = "Audi"; private _color = 'red'; private _maxSpeed = 150; drive() { console.log(`WOW! You are riding your cool ${this._color} ${this._name} at ${this._maxSpeed} kmh!`); } paint(color: string) { this._color = color; } } class Plane extends Singleton<Plane> { private _motors = 4; private _maxSpeed = 550; fly() { console.log(`WOW! You are flying your plane with roaring ${this._motors} motors at ${this._maxSpeed} kmh!`); } }
使用方式
const car = Car.getInstance(); car.drive(); // 输出对应内容 const plane = Plane.getInstance(); plane.fly(); // 直接new会抛出错误,强制使用单例 // new Car(); // Error: 请通过getInstance()获取单例实例
方案二:高阶函数包装器(JS/TS通用)
通过包装器函数给普通类注入单例逻辑,无需修改原有类的结构,侵入性更低:
function makeSingleton<T extends new (...args: any[]) => any>(OriginalClass: T): T & { getInstance: () => InstanceType<T> } { let instance: InstanceType<T>; // 用Proxy拦截new操作,确保只创建一个实例 const SingletonClass = new Proxy(OriginalClass, { construct(target, args) { if (!instance) { instance = new target(...args); } return instance; } }); // 添加getInstance静态方法 SingletonClass.getInstance = () => { if (!instance) { instance = new OriginalClass(); } return instance; }; return SingletonClass as T & { getInstance: () => InstanceType<T> }; }
使用示例
// 定义普通类,无需任何单例相关代码 class Car { private _name = "Audi"; private _color = 'red'; private _maxSpeed = 150; drive() { console.log(`WOW! You are riding your cool ${this._color} ${this._name} at ${this._maxSpeed} kmh!`); } paint(color: string) { this._color = color; } } class Plane { private _motors = 4; private _maxSpeed = 550; fly() { console.log(`WOW! You are flying your plane with roaring ${this._motors} motors at ${this._maxSpeed} kmh!`); } } // 包装成单例类 const SingletonCar = makeSingleton(Car); const SingletonPlane = makeSingleton(Plane); // 两种调用方式都返回同一个实例 const car1 = SingletonCar.getInstance(); const car2 = new SingletonCar(); console.log(car1 === car2); // true const plane1 = SingletonPlane.getInstance(); const plane2 = new SingletonPlane(); console.log(plane1 === plane2); // true
强制单例的保障
- 抽象基类方案:构造函数设为
protected,外部无法直接new子类;基类构造时会检查实例是否存在,防止子类内部违规创建。 - 包装器方案:Proxy拦截所有
new操作,无论调用多少次都返回同一个实例;同时getInstance方法也确保实例唯一。
内容的提问来源于stack exchange,提问作者aviya.developer
相关产品推荐
相关产品推荐

