原型模式¶
分类:创建型模式。
类结构¶
classDiagram
direction TB
class Client {
+duplicate()
}
class Prototype {
<<interface>>
+clone() Prototype
}
class ConcretePrototypeA {
+clone() Prototype
}
class ConcretePrototypeB {
+clone() Prototype
}
Client --> Prototype : 请求复制
Prototype <|.. ConcretePrototypeA
Prototype <|.. ConcretePrototypeB
角色定义¶
| 角色 | 定义 |
|---|---|
| Client(客户) | 持有原型引用,通过复制现有对象获得新对象。 |
| Prototype(原型接口) | 声明返回对象副本的复制操作。 |
| ConcretePrototypeA / ConcretePrototypeB(具体原型) | 实现自身的复制逻辑,决定如何复制内部状态和引用;深拷贝或浅拷贝由具体实现约定。 |
Head First 示例:复制已有的怪物配置¶
本节对应《Head First 设计模式》附录中的原型主题,具体类型与行为按作者 2020 年第二版配套代码 prototype 下的 Java 实现整理。在需要沿用某只怪物的名称、翅膀、头数和喷火能力时,调用者可以通过 Monster.copy() 复制已有配置,再为副本改名。Dragon 与 Drakon 都继承 Monster,各自实现复制操作;图中的名称来自配套源码,类图是本节据此整理的示意。
Monster 实现 Java 的标记接口 Cloneable,并声明抽象的 copy();两个子类的实现都调用继承的 clone(),因此这里执行的是浅克隆。Cloneable 本身没有声明 copy()。同目录的 Client.java 使用 Prototype、ConcretePrototype1 和 ConcretePrototype2,没有调用这组怪物类;以下协作过程是依据怪物 Java 类整理的调用说明,同目录网页中的脚本不作为 Java 克隆流程的依据。
| 具体类名 | 对应角色 | 职责 |
|---|---|---|
Cloneable |
Java 克隆标记接口 | 标记实例支持通过 Object.clone() 进行字段复制,本身没有复制方法。 |
Monster |
抽象原型 | 保存怪物状态,声明 copy(),提供 setName() 和默认不执行动作的 spitPoison()。 |
Dragon |
具体原型 | 构造时设置是否有翅膀,并开启喷火能力;实现 copy() 返回副本。 |
Drakon |
具体原型 | 构造时设置头数和喷火能力,重写 spitPoison(),并实现 copy()。 |
示例 UML¶
图中保留 4 个类型,省略 Object、异常类型、部分状态字段及文字输出方法。~ 表示包级可见,* 标记抽象方法。copy() 的三个声明都带有 throws CloneNotSupportedException,图内为简洁未列出异常声明;两个具体实现的返回类型均为 Monster。
classDiagram
direction TB
class Cloneable {
<<interface>>
}
class Monster {
<<abstract>>
~String name
~boolean hasWings
~int numHeads
~boolean canBreatheFire
+Monster(String name)
+copy() Monster*
+setName(String name) void
+spitPoison() void
}
class Dragon {
+Dragon(String name, boolean hasWings)
+copy() Monster
}
class Drakon {
+Drakon(String name, int numHeads, boolean canBreatheFire)
+copy() Monster
+spitPoison() void
}
Cloneable <|.. Monster
Monster <|-- Dragon
Monster <|-- Drakon
协作过程¶
- 调用者先通过构造器创建
Dragon或Drakon作为原型,并用Monster引用保存它。例如,Drakon的构造参数可以确定名称、头数和是否喷火。 - 调用者请求
copy(),并按其方法声明处理或继续抛出CloneNotSupportedException。动态分派进入实际子类的复制实现,后者调用clone()并返回新的Monster引用。 - 副本是独立对象,保留原对象的实际运行时类型和字段值;复制
Drakon得到的仍是Drakon,因此它仍使用重写的spitPoison()。 - 调用副本的
setName()只会替换副本的名称引用,不会改变原型名称。当前字段主要是基本类型和不可变的String;如果以后加入可变引用字段,现有浅克隆会共享这些对象,需要另行决定是否深拷贝。
固定版本源码: