0116_原型模式(Prototype)
原型模式 (Prototype Pattern)
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式的核心在于“复制”或“克隆”一个现有的、已经配置好的对象,而不是通过new关键字重新构建并一步步设置其状态。这在以下场景非常有用:
- 当一个系统应该独立于它的产品创建、构成和表示时。
- 当要实例化的类是在运行时指定时(例如,通过动态加载)。
- 避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
UML 图

- Prototype (原型接口):声明一个克隆自身的接口(在Java中通常是实现
Cloneable接口并重写clone方法)。 - ConcretePrototype (具体原型):实现克隆操作的具体类。
- Client (客户端):通过请求原型克隆自身来创建一个新的对象。
优点
- 性能优良:逃避构造函数的约束,直接复制内存二进制流,性能比直接
new一个对象好很多。 - 逃避构造函数的约束:克隆不会调用类的构造函数,因此可以避免构造函数中的一些限制或副作用。
- 简化对象创建:隐藏了创建新实例的复杂性,客户端无需知道对象的创建细节。对于构建过程复杂的对象(比如您例子中的
Human),复制一个现成的比重新构建一个要简单得多。 - 动态获取对象运行时状态:可以保存一个对象在某一时刻的状态,并在需要时复制它。
缺点
- 深拷贝与浅拷贝问题:实现克隆需要仔细考虑。默认的
Object.clone()方法是浅拷贝(Shallow Copy)。如果对象内部有引用其他对象(非基本类型、不可变对象如String),则需要重写clone方法实现深拷贝(Deep Copy),否则克隆对象和原对象会共享这些引用,这可能不是你想要的结果。深拷贝实现起来可能比较复杂。 - 对克隆方法的改造需要修改所有具体原型类的代码:如果需要在克隆过程中加入新的通用逻辑,需要修改所有原型类的
clone方法。 - 违背“构造函数中不包含复杂逻辑”的设计原则:因为克隆不走构造函数,如果对象构建本身依赖于构造函数的复杂初始化逻辑,克隆可能会带来问题。
代码示例
建造者模式中,正常人包含头、手臂、腿、身体。这里让我们用原型模式来实现创建不同类型Human的需求。
1. 产品类 (Product) - 实现Cloneable接口
// 人类对象,现在它自己就是一个可被克隆的原型
public class Human implements Cloneable {
// 头部属性
private String head;
// 手臂属性
private String arms;
// 腿部属性
private String legs;
// 身体属性
private String body;
/**
* 设置头部
* @param head 头部描述
*/
public void setHead(String head) { this.head = head; }
/**
* 设置手臂
* @param arms 手臂描述
*/
public void setArms(String arms) { this.arms = arms; }
/**
* 设置腿部
* @param legs 腿部描述
*/
public void setLegs(String legs) { this.legs = legs; }
/**
* 设置身体
* @param body 身体描述
*/
public void setBody(String body) { this.body = body; }
/**
* 重写clone方法,提供公共的克隆能力
* 这里使用Object的默认clone方法(浅拷贝)对于String属性是安全的,
* 因为String是不可变对象。如果Human内有可变对象的引用,则需要深拷贝。
* @return 返回克隆后的Human对象
*/
@Override
public Human clone() {
try {
return (Human) super.clone(); // 调用Object的native clone方法
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Human实现了Cloneable,不会发生此异常
}
}
/**
* 打印人类信息,用于验证
*/
public void printInfo() {
System.out.println("Head: " + head);
System.out.println("Body: " + body);
System.out.println("Arms: " + arms);
System.out.println("Legs: " + legs);
System.out.println("---------------");
}
}
2. 客户端代码 (Client Code)
public class PrototypeTest {
public static void main(String[] args) {
// 1. 创建原型对象(类似于建造者模式中的Director和Builder构建出的完整产品)
Human normalHumanPrototype = new Human();
normalHumanPrototype.setHead("头部 - 完整");
normalHumanPrototype.setBody("身体 - 完整");
normalHumanPrototype.setArms("手臂 - 完整");
normalHumanPrototype.setLegs("腿部 - 完整");
System.out.println("原型对象:");
normalHumanPrototype.printInfo();
// 2. 客户端通过克隆原型来创建新对象,而不是使用new和set方法
// 克隆一个正常人
Human normalHumanClone = normalHumanPrototype.clone();
System.out.println("克隆的正常人:");
normalHumanClone.printInfo();
// 3. 可以在克隆的基础上进行微调,而无需重新构建整个对象
Human specialHuman = normalHumanPrototype.clone(); // 基于正常人克隆
specialHuman.setBody("身体 - 特别强壮"); // 只修改身体部分
System.out.println("微调后的特殊人类:");
specialHuman.printInfo();
// 验证克隆是不同的对象 (输出false)
System.out.println("normalHumanPrototype == normalHumanClone : " + (normalHumanPrototype == normalHumanClone));
}
}
在Spring框架中的应用
Spring框架中广泛使用了原型模式,最典型的就是Bean的作用域(Scope):
-
原型作用域的Bean (Prototype-scoped Beans)
在Spring配置中,将一个bean的scope属性设置为prototype,每次从容器中获取该bean时,Spring都会克隆一个原型bean来创建一个新的实例。<bean id="myPrototypeBean" class="com.example.MyBean" scope="prototype"/>或使用注解:
@Component @Scope("prototype") public class MyPrototypeBean { ... } -
ObjectFactory或Provider
为了延迟获取原型bean(避免在注入时就固定一个实例),Spring提供了ObjectFactory或JSR-330的Provider接口。每次调用getObject()方法时,容器会返回一个新的原型bean实例。@Autowired private ObjectFactory<MyPrototypeBean> myPrototypeBeanFactory; public void doSomething() { MyPrototypeBean newInstance = myPrototypeBeanFactory.getObject(); // ... 使用新的实例 }
总结
原型模式提供了一种基于已有对象创建新对象的有效途径,它绕过了传统的构造方法,通过复制(克隆)来快速创建状态复杂的对象。它在需要高性能创建复杂对象、或者需要隔离客户端与具体产品类的场景下非常有用。

浙公网安备 33010602011771号