JavaScript设计模式「基于ES2024」:创建型模式-原型模式

80 阅读2分钟

原型模式让开发者可以轻松复制已有对象,而无需使代码依赖它们所属的类。这种模式在需要创建对象的精确副本或者当直接创建对象的代价比较大时特别有用。

PS:在javascript中实现原型模式难以想象的简单

// 原型接口
class Prototype {
    clone() {
        throw new Error('clone method must be implemented');
    }
}

// 具体原型
class Shape extends Prototype {
    #color;
    #x;
    #y;

    constructor(color = 'red', x = 0, y = 0) {
        super();
        this.#color = color;
        this.#x = x;
        this.#y = y;
    }

    clone() {
        // 创建一个新的实例并复制所有字段
        return new Shape(this.#color, this.#x, this.#y);
    }

    move(x, y) {
        this.#x = x;
        this.#y = y;
    }

    get info() {
        return `${this.constructor.name} (color: ${this.#color}, x: ${this.#x}, y: ${this.#y})`;
    }
}

class Circle extends Shape {
    #radius;

    constructor(color, x, y, radius) {
        super(color, x, y);
        this.#radius = radius;
    }

    clone() {
        return new Circle(this.color, this.x, this.y, this.#radius);
    }

    get info() {
        return `${super.info}, radius: ${this.#radius}`;
    }
}

class Rectangle extends Shape {
    #width;
    #height;

    constructor(color, x, y, width, height) {
        super(color, x, y);
        this.#width = width;
        this.#height = height;
    }

    clone() {
        return new Rectangle(this.color, this.x, this.y, this.#width, this.#height);
    }

    get info() {
        return `${super.info}, width: ${this.#width}, height: ${this.#height}`;
    }
}

// 原型注册表
class ShapeRegistry {
    static #prototypes = new Map();

    static registerPrototype(key, prototype) {
        if (!(prototype instanceof Prototype)) {
            throw new Error('Object must implement Prototype interface');
        }
        this.#prototypes.set(key, prototype);
    }

    static getPrototype(key) {
        const prototype = this.#prototypes.get(key);
        if (!prototype) {
            throw new Error(`Prototype with key ${key} not found`);
        }
        return prototype.clone();
    }
}

// 使用示例
const circle = new Circle('blue', 10, 10, 5);
const rectangle = new Rectangle('green', 20, 20, 15, 10);

ShapeRegistry.registerPrototype('smallBlueCircle', circle);
ShapeRegistry.registerPrototype('mediumGreenRectangle', rectangle);

const clonedCircle = ShapeRegistry.getPrototype('smallBlueCircle');
clonedCircle.move(30, 30);

const clonedRectangle = ShapeRegistry.getPrototype('mediumGreenRectangle');
clonedRectangle.move(50, 50);

console.log(circle.info);        // 原始圆形
console.log(clonedCircle.info);  // 克隆并移动的圆形
console.log(rectangle.info);     // 原始矩形
console.log(clonedRectangle.info); // 克隆并移动的矩形

实现思路:

  • Prototype 接口:
    • 定义了 clone() 方法,所有可克隆的对象都必须实现这个接口。通过强制实现 clone 方法,确保所有子类都具备克隆能力。
  • 具体原型类 Shape:
    • Shape 类使用私有字段(#color, #x, #y)存储形状的属性。
    • 实现 clone() 方法,返回当前实例的一个新副本。
    • 提供 move() 方法来修改形状的位置。
    • 通过 info 方法返回形状的详细信息。
  • 具体子类 CircleRectangle:
    • CircleRectangle 类分别继承自 Shape,并添加了各自特有的属性(如半径、宽度和高度)。
    • 重写 clone() 方法,以确保在克隆时正确复制所有特定属性。
    • 重写 info 方法,包含所有属性的详细信息。
  • 原型注册表 ShapeRegistry:
    • 使用静态私有字段 #prototypes 存储原型对象。
    • 提供 registerPrototype() 方法来注册新的原型对象,确保对象实现 Prototype 接口。
    • 提供 getPrototype() 方法,通过键获取并克隆注册的原型对象。

优点:

  • 减少子类数量: 克隆现有对象比创建新的子类更灵活。
  • 动态配置对象: 可以在运行时动态添加或删除对象。
  • 减少重复的初始化代码: 特别是当对象的初始化过程很复杂时。
  • 更灵活的层次结构: 可以通过组合不同的原型来创建新的对象。