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. 内存消耗:大量使用克隆方法可能会导致过多的内存消耗,需要合理管理对象生命周期。

总结

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