Published on

原型模式详解

Authors
  • avatar
    Name
    青雲
    Twitter

在软件设计中,原型模式(Prototype Pattern)是一种创建型设计模式。它的主要思想是通过复制已有的实例来创建新对象,而不是通过类构造器来创建。这种模式特别适用于对象的创建代价较高,或者需要多个几乎相同的对象时。通过原型模式,我们可以高效地创建对象,并且更灵活地管理对象的状态。

为什么需要原型模式?

在某些情况下,创建对象是一个昂贵的操作,可能涉及复杂的计算、网络请求或数据库查询。通过原型模式,我们可以通过复制现有对象,快速创建新对象,避免重复复杂的创建过程。 另外,原型模式还有助于简化对象创建的代码结构,使得代码更加灵活和易于维护。

基本结构

原型模式通常由以下几个部分组成:

  1. 原型接口(Prototype Interface):声明克隆方法。
  2. 具体原型类(Concrete Prototype):实现克隆方法,负责复制自身。
  3. 客户端(Client):调用克隆方法来创建新对象。

示例

定义原型接口

首先,我们定义一个 Cloneable 接口,包含一个 clone 方法。

interface Cloneable {
  clone(): this;
}

实现具体原型类

具体原型类实现 Cloneable 接口,并实现 clone 方法。

class Person implements Cloneable {
  constructor(public name: string, public age: number, public address: Address) {}

  clone(): this {
    const cloneObj = Object.create(this);
    cloneObj.address = Object.assign({}, this.address);
    return cloneObj;
  }
}

class Address {
  constructor(public street: string, public city: string) {}
}

使用原型模式创建对象

我们通过克隆方法来创建新对象,并展示原型模式的效果。

const originalPerson = new Person("Han Mei", 30, new Address("Yuehai St", "Shenzhen"));

const clonedPerson = originalPerson.clone();
clonedPerson.name = "Li Lei";
clonedPerson.address.street = "Nanshan St";

console.log(`Original Person: ${originalPerson.name}, ${originalPerson.address.street}`); 
// 输出: Original Person: Han Mei, Yuehai St

console.log(`Cloned Person: ${clonedPerson.name}, ${clonedPerson.address.street}`); 
// 输出: Cloned Person: Li Lei, Nanshan St

深拷贝示例

在前面的示例中,我们进行了浅拷贝,但对于更复杂的对象,还可能需要进行深拷贝。 实现深拷贝,可以采用递归的方法,或者使用 JSON.parseJSON.stringify 来实现。 我们将定义一个递归函数来实现深拷贝,这个函数能够处理嵌套对象和数组。

function deepClone<T>(obj: T): T {
  // 处理 null 或者基础类型(比如:string, number, boolean)
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理 Array
  if (Array.isArray(obj)) {
    const arrCopy = [] as any[];
    obj.forEach((item, index) => {
      arrCopy[index] = deepClone(item);
    });
    return arrCopy as unknown as T;
  }

  // 处理 Object
  const objCopy = {} as T;
  Object.keys(obj).forEach((key: keyof T) => {
    objCopy[key] = deepClone(obj[key]);
  });
  return objCopy;
}

然后将使用上述递归实现的深拷贝方法在前面的 Person 类中。

class Address {
  constructor(public street: string, public city: string) {}
}

class Person implements Cloneable {
  constructor(public name: string, public age: number, public address: Address) {}

  clone(): this {
    return deepClone(this);
  }
}

interface Cloneable {
  clone(): this;
}

应用场景

对象创建

假设我们需要创建一个网页组件 Button,它有多个配置选项。我们可以使用原型模式来创建不同配置的按钮对象。

interface Cloneable {
  clone(): this;
}

class Button implements Cloneable {
  constructor(public label: string, public width: number, public height: number, public color: string) {}

  clone(): this {
    const cloneObj = Object.create(this);
    return cloneObj;
  }

  render(): void {
    console.log(`Button: ${this.label}, Width: ${this.width}, Height: ${this.height}, Color: ${this.color}`);
  }
}

// 创建一个按钮原型
const defaultButton = new Button("Submit", 100, 50, "blue");
defaultButton.render(); // 输出: Button: Submit, Width: 100, Height: 50, Color: blue

// 使用原型模式创建新按钮
const customButton = defaultButton.clone();
customButton.label = "Cancel";
customButton.color = "red";
customButton.render(); // 输出: Button: Cancel, Width: 100, Height: 50, Color: red

配置对象

在前端开发中,我们常常需要使用配置对象来初始化组件。通过原型模式,可以基于默认配置对象创建新的配置对象,方便快速定制不同的配置。

interface Config extends Cloneable {
  theme: string;
  layout: string;
  showSidebar: boolean;
}

class AppConfig implements Config {
  constructor(public theme: string, public layout: string, public showSidebar: boolean) {}
  
  clone(): this {
    const cloneObj = Object.create(this);
    return cloneObj;
  }

  display(): void {
    console.log(`Theme: ${this.theme}, Layout: ${this.layout}, Show Sidebar: ${this.showSidebar}`);
  }
}

// 创建默认配置
const defaultConfig = new AppConfig("light", "grid", true);
defaultConfig.display(); // 输出: Theme: light, Layout: grid, Show Sidebar: true

// 基于默认配置创建新的配置
const customConfig = defaultConfig.clone();
customConfig.theme = "dark";
customConfig.display(); // 输出: Theme: dark, Layout: grid, Show Sidebar: true

状态管理

在前端应用中,常常需要复制状态对象,方便创建新状态或回滚到之前的状态。

interface State extends Cloneable {
  user: string;
  isLoggedIn: boolean;
  preferences: object;
}

class AppState implements State {
  constructor(public user: string, public isLoggedIn: boolean, public preferences: object) {}

  clone(): this {
    const cloneObj = Object.create(this);
    cloneObj.preferences = { ...this.preferences };
    return cloneObj;
  }

  display(): void {
    console.log(`User: ${this.user}, Is Logged In: ${this.isLoggedIn}, Preferences: ${JSON.stringify(this.preferences)}`);
  }
}

// 创建初始状态
const initialState = new AppState("Alice", true, { theme: "light", language: "en" });
initialState.display(); // 输出: User: Alice, Is Logged In: true, Preferences: {"theme":"light","language":"en"}

// 基于初始状态创建新状态
const newState = initialState.clone();
newState.user = "Bob";
newState.preferences.theme = "dark";
newState.display(); // 输出: User: Bob, Is Logged In: true, Preferences: {"theme":"dark","language":"en"}

图形对象

在图形和动画处理中,原型模式可以用来复制图形对象,从而生成多个相似的图形实例,便于图形的管理和操作。

interface Graphic extends Cloneable {
  draw(): void;
}

class Circle implements Graphic {
  constructor(public radius: number, public color: string) {}

  clone(): this {
    const cloneObj = Object.create(this);
    return cloneObj;
  }

  draw(): void {
    console.log(`Drawing a ${this.color} circle with radius ${this.radius}`);
  }
}

// 创建一个图形原型
const defaultCircle = new Circle(10, "blue");
defaultCircle.draw(); // 输出: Drawing a blue circle with radius 10

// 使用原型模式创建新图形
const customCircle = defaultCircle.clone();
customCircle.radius = 20;
customCircle.color = "red";
customCircle.draw(); // 输出: Drawing a red circle with radius 20

Lodash 对象拷贝

Lodash 是一个流行的工具库,广泛用于处理数组、对象、字符串等。Lodash 中的 _.clone()_.cloneDeep() 方法用于实现对象的浅拷贝和深拷贝。

const _ = require('lodash');

const obj = { a: 1, b: { c: 3 } };
const shallowCopy = _.clone(obj);
const deepCopy = _.cloneDeep(obj);

console.log(shallowCopy); // { a: 1, b: { c: 3 } }
console.log(deepCopy); // { a: 1, b: { c: 3 } }

// 修改原对象不会影响深拷贝的对象
obj.b.c = 4;
console.log(obj); // { a: 1, b: { c: 4 } }
console.log(deepCopy); // { a: 1, b: { c: 3 } }

原型模式的优缺点

优点

  1. 快速创建对象:通过克隆现有对象,可以快速创建新对象,适用于创建代价高的对象。
  2. 简化对象创建过程:避免了直接使用构造函数来创建对象,代码更加简洁。
  3. 灵活性高:可以动态改变对象的状态,同时生成多个副本。

缺点

  1. 深拷贝复杂:对于包含引用类型的复杂对象,实现深拷贝较为复杂,需要小心处理对象之间的引用关系。
  2. 内存消耗:大量使用克隆方法可能会导致过多的内存消耗,需要合理管理对象生命周期。

总结

原型模式是一种强大的设计模式,通过复制现有对象来创建新对象,节省了对象创建的成本,使得代码更加灵活和易于维护。在前端开发中,尤其是在需要大量生成相似对象的场景中,原型模式提供了高效的解决方案。