模式补全
这一节按 八面剖析以理解一个概念 / ljg-learn 的思路整理为 Markdown 版本:先定锚,再用八个方向切开概念,最后压缩成公式、例子、类图和检验题。
定锚
原型模式(创建型)的通行定义:通过复制已有对象来创建新对象,避免重新走复杂构造流程。
常见误解:它不是简单浅拷贝;关键在于对象自己定义怎样复制才算正确。
核心词素:Prototype 的 proto 是先在场的模型;type 是类型。它用一个已成形的样本生成同类对象。
八刀
历史
它在 Smalltalk 等原型式系统中很自然,JavaScript 的原型链也让这种思想更显眼。GoF 把它收为创建型模式。
辩证
反面是每次都从零构造。更高理解是:当构造成本高或运行时类型才确定时,复制比创建更自然。
现象
像复制一份简历模板,再改姓名和经历。你不重新排版,只复制已有结构。
语言
Prototype 的 proto 是先在场的模型;type 是类型。它用一个已成形的样本生成同类对象。
形式
newObject = prototype.clone()。当对象含有外部资源、循环引用或共享状态时,clone 边界会变难。
存在
它让对象拥有自我繁殖能力,创建权从类转移到实例。
美感
它美在影印:一个复杂形状不再被重画,而是在纸上迅速显影。
元反思
我们用“复制”隐喻理解它,容易忽略复制语义。换成“谱系”隐喻,会更注意哪些状态该继承、哪些该重置。
内观
我是样本。你不必知道我怎么出生,只要让我 clone,我会给你一个合适的新我。
八刀共同指向的深层结构:原型模式不是为了“炫技”,而是在某个变化点上建立边界,让稳定部分继续稳定,让变化部分有自己的位置。
压缩
公式:原型 = 样本对象 + clone 规则 + 运行时复制
一句话:原型模式让对象从已有对象长出来,而不是每次从类构造器里硬造。
结构图:
Client -> Prototype.clone() -> Copy动机/意图
当对象创建成本高、构造过程复杂,或具体类型只能在运行时确定时,每次从类开始重新构造并不合适。原型模式的意图是让已有对象负责复制自身,用 clone() 生成结构和初始状态都合适的新对象。
结构/角色
Prototype:原型接口,声明clone()。ConcretePrototype:具体原型,实现复制规则,决定深拷贝、浅拷贝和状态重置边界。Client:客户端持有或查找原型,通过复制得到新对象。PrototypeRegistry:原型管理器,可选角色,用名称或 key 管理一组可复制原型。
典型 UML
classDiagram class Client class Prototype { <<interface>> +clone(): Prototype } class ConcretePrototypeA { +clone(): ConcretePrototypeA } class ConcretePrototypeB { +clone(): ConcretePrototypeB } Prototype <|.. ConcretePrototypeA Prototype <|.. ConcretePrototypeB Client ..> Prototype
使用场景
- 对象初始化昂贵,需要复用一个预配置模板。
- 运行时才知道具体对象类型,不希望客户端依赖具体构造器。
- 需要快速生成大量相似对象,只修改少量字段。
- 希望对象自己定义复制语义,避免外部误用浅拷贝。
正例:TypeScript
interface Prototype<T> {
clone(): T
}
class Attachment implements Prototype<Attachment> {
constructor(
public name: string,
public content: string,
) {}
clone() {
return new Attachment(this.name, this.content)
}
}
class Email implements Prototype<Email> {
constructor(
public sender: string,
public receiver: string,
public subject: string,
public content: string,
public date: string,
public attachment: Attachment,
) {}
// 浅克隆:复制邮件本身,但共享附件对象
clone() {
return new Email(
this.sender,
this.receiver,
this.subject,
this.content,
this.date,
this.attachment,
)
}
// 深克隆:连附件也一起复制
deepClone() {
return new Email(
this.sender,
this.receiver,
this.subject,
this.content,
this.date,
this.attachment.clone(),
)
}
}
const source = new Email(
"alice@example.com",
"team@example.com",
"weekly report",
"please check the report",
"2026-06-24",
new Attachment("report.pdf", "pdf-binary-data"),
)
const shallowCopied = source.clone()
shallowCopied.subject = "weekly report copy"
shallowCopied.attachment.name = "updated-report.pdf"
const deepCopied = source.deepClone()
deepCopied.subject = "weekly report deep copy"
deepCopied.attachment.name = "deep-report.pdf"正例:UML 类图
classDiagram class Prototype { <<interface>> +clone() } class Attachment { +name: string +content: string +clone(): Attachment } class Email { +sender: string +receiver: string +subject: string +content: string +date: string +attachment: Attachment +clone(): Email +deepClone(): Email } Prototype <|.. Attachment Prototype <|.. Email Email --> Attachment Email ..> Email : clone Attachment ..> Attachment : clone
反例:TypeScript
class Attachment {
constructor(
public name: string,
public content: string,
) {}
}
const source = {
sender: "alice@example.com",
receiver: "team@example.com",
subject: "weekly report",
content: "please check the report",
date: "2026-06-24",
attachment: new Attachment("report.pdf", "pdf-binary-data"),
}
const copied = { ...source }
copied.subject = "weekly report copy"
copied.attachment.name = "updated-report.pdf"
// source.attachment.name 也会一起变化,因为这里只是随手做了浅拷贝反例:UML 类图
classDiagram class Client class EmailData { +sender: string +receiver: string +subject: string +content: string +date: string +attachment: Attachment } class Attachment { +name: string +content: string } Client ..> EmailData : manual copy EmailData --> Attachment : shared reference
案例
由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。在本实例中使用浅克隆实现邮件复制,即复制邮件(Email)的同时不复制附件(Attachment)。
使用深克隆实现邮件复制,即复制邮件的同时复制附件。
掌握检验
- 原型模式适合什么样的创建成本?
- 浅拷贝为什么可能破坏原型模式的语义?
- JavaScript 的原型链和 GoF 原型模式有什么联系与区别?