原型模式
1/9/24About 6 min
Prototype模式允许对象在不了解要创建对象的确切类以及如何创建等细节的情况下创建自定义对象。使用Prototype实例,便指定了要创建的对象类型,而通过复制这个Prototype,就可以创建新的对象。Prototype模式是通过先给出一个对象的Prototype对象,然后再初始化对象的创建。创建初始化后的对象再通过Prototype对象对其自身进行复制来创建其他对象。Prototype模式使得动态创建对象更加简单,只要将对象类定义能够复制自身就可以实现。
基于Cloneable接口的代码实现
定义抽象类
public abstract class Prototype {
private String name;
private String price;
private A a = new A("a");
public Prototype() {
}
public Prototype(String name, String price) {
this.name = name;
this.price = price;
}
/**
* 自定义的复制方法
*/
public abstract Prototype cloneObject();
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPrice() {
return this.price;
}
public A getA() {
return this.a;
}
public void setA(A a) {
this.a = a;
}
public void setPrice(String price) {
this.price = price;
}
@Override
public String toString() {
return "{" +
" name='" + getName() + "'" +
", price='" + getPrice() + "'" +
", a='" + getA() + "'" +
"}";
}
}
class A {
private String name;
public A(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}定义子类
ConcretePrototype1实现了Cloneable接口,并重写了clone方法,实现了对象的复制。
public class ConcretePrototype1 extends Prototype implements Cloneable {
public ConcretePrototype1(String name, String price) {
super(name, price);
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public Prototype cloneObject() {
throw new UnsupportedOperationException("Unimplemented method 'clone'");
}
}测试
public class PrototypeClient {
public static void main(String[] args) {
// 浅拷贝 Nikola Zhang 【2018/12/14 0014 20:51】
ConcretePrototype1 prototype1 = new ConcretePrototype1("AAA", "1111");
ConcretePrototype1 prototype1Copy = (ConcretePrototype1) prototype1.clone();
System.out.println(prototype1);
System.out.println(prototype1Copy);
System.out.println(prototype1 == prototype1Copy);
System.out.println(prototype1.getA() == prototype1Copy.getA());
}
}结果

根据结果截图,可以看到引用类型对象输出的内容是相同的,但是地址是不同的。但内部的引用类型A地址确实相同的。该方式实现了浅拷贝的方式。 这就引出了一个额外的话题,深拷贝。深拷贝可以通过实现Cloneable接口实现,也可以通过实现Serializable接口实现。
基于Serializable接口的深拷贝
需要注意的是,ConcretePrototype2中的引用类型必须实现Serializable接口,否则会报错。
public class ConcretePrototype2 extends Prototype implements Serializable {
public ConcretePrototype2() {
super();
}
public ConcretePrototype2(String name, String price) {
super(name, price);
}
@Override
public Prototype cloneObject() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(this);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
return null;
}
// 从字节流中读取对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
try (ObjectInputStream ois = new ObjectInputStream(bis)) {
return (Prototype) ois.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
return null;
}
}
}测试深拷贝
public class PrototypeClient {
public static void main(String[] args) {
ConcretePrototype2 prototype2 = new ConcretePrototype2("AAA", "1111");
ConcretePrototype2 prototype2Copy = (ConcretePrototype2) prototype2.cloneObject();
System.out.println(prototype2);
System.out.println(prototype2Copy);
System.out.println(prototype2 == prototype2Copy);
System.out.println(prototype2.getA() == prototype2Copy.getA());
}
}深拷贝结果

可以看到两个对象的引用地址是不同的,并且内部的引用类型A地址是不同的。
其他
我还看过一个使用缓存的例子,菜鸟教程-原型模式。
在这个例子中,实际获取对象的时候也是使用了浅拷贝。
Cloneable接口实现。

从缓存中获取对象并拷贝。

与其他设计模式的对比
原型模式 vs 工厂方法模式
| 特性 | 原型模式 | 工厂方法模式 |
|---|---|---|
| 创建方式 | 通过复制现有对象创建新对象 | 通过子类创建对象实例 |
| 对象初始化 | 基于现有对象的状态 | 从头开始初始化 |
| 性能 | 高(避免了重复的初始化过程) | 一般(需要完整的初始化过程) |
| 适用场景 | 对象创建成本高,需频繁创建 | 产品类型多样,需灵活扩展 |
| 代码复杂度 | 低(只需实现Cloneable接口) | 中(需创建多个工厂类) |
原型模式 vs 抽象工厂模式
| 特性 | 原型模式 | 抽象工厂模式 |
|---|---|---|
| 产品范围 | 单个对象的复制 | 产品家族的创建 |
| 创建方式 | 通过复制现有对象 | 通过工厂创建新对象 |
| 灵活性 | 高(可动态改变对象状态后复制) | 中(需提前定义产品家族) |
| 性能 | 高(避免了重复初始化) | 一般(需完整初始化过程) |
| 适用场景 | 需频繁创建相似对象 | 需创建相关或相互依赖的对象家族 |
原型模式 vs 建造者模式
| 特性 | 原型模式 | 建造者模式 |
|---|---|---|
| 目的 | 高效创建相似对象 | 构建复杂对象的不同表示 |
| 构建过程 | 单一复制操作 | 分步构建过程 |
| 对象复杂度 | 适用于任何复杂度的对象 | 特别适用于复杂对象 |
| 客户端参与度 | 低(只需调用clone方法) | 高(需指导构建过程) |
| 扩展性 | 中(需注意深拷贝的实现) | 高(可灵活扩展构建步骤) |
总结
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是重新初始化新对象。这种方式可以有效降低对象创建的成本,提高系统性能。
核心思想
通过复制一个已经存在的对象来创建新对象,而不需要了解对象创建的细节。原型模式的关键是实现对象的克隆方法,支持浅拷贝和深拷贝两种方式。
优点
- 性能优化:避免了重复的对象初始化过程,特别适合创建成本高的对象
- 简化对象创建:无需了解对象创建的细节,只需调用克隆方法
- 动态扩展:可以在运行时动态改变原型对象的状态,然后复制出多个具有不同状态的对象
- 降低耦合度:客户端与具体类解耦,只依赖于原型接口
缺点
- 深拷贝实现复杂:对于包含复杂引用类型的对象,深拷贝的实现比较复杂
- 违反单一职责原则:对象需要同时负责自身的业务逻辑和克隆逻辑
- 隐藏的风险:如果对象的内部结构发生变化,克隆方法可能需要相应修改
适用场景
- 对象创建成本高:当对象的创建需要消耗大量资源或时间时
- 需要频繁创建相似对象:例如在游戏开发中创建大量相似的游戏角色
- 需要动态生成对象类型:在运行时根据条件确定要创建的对象类型
- 希望避免构造函数的约束:当构造函数参数复杂或难以获取时
浅拷贝与深拷贝
- 浅拷贝:只复制对象本身和基本类型成员,引用类型成员仍然指向原对象的引用
- 深拷贝:复制对象本身和所有成员,包括引用类型成员,实现真正的完全复制
实际应用
在实际开发中,原型模式常用于:
- Java中的Cloneable接口和clone()方法
- Spring框架中的Bean作用域管理
- 游戏开发中的对象复制(如角色、道具等)
- 分布式系统中的对象序列化和反序列化
- 缓存系统中的对象复制,避免直接返回缓存对象导致的并发问题