0116_原型模式(Prototype)

原型模式 (Prototype Pattern)

意图

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式的核心在于“复制”或“克隆”一个现有的、已经配置好的对象,而不是通过new关键字重新构建并一步步设置其状态。这在以下场景非常有用:

  1. 当一个系统应该独立于它的产品创建、构成和表示时。
  2. 当要实例化的类是在运行时指定时(例如,通过动态加载)。
  3. 避免创建一个与产品类层次平行的工厂类层次时。
  4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。

UML 图

image

  • Prototype (原型接口):声明一个克隆自身的接口(在Java中通常是实现Cloneable接口并重写clone方法)。
  • ConcretePrototype (具体原型):实现克隆操作的具体类。
  • Client (客户端):通过请求原型克隆自身来创建一个新的对象。

优点

  1. 性能优良:逃避构造函数的约束,直接复制内存二进制流,性能比直接new一个对象好很多。
  2. 逃避构造函数的约束:克隆不会调用类的构造函数,因此可以避免构造函数中的一些限制或副作用。
  3. 简化对象创建:隐藏了创建新实例的复杂性,客户端无需知道对象的创建细节。对于构建过程复杂的对象(比如您例子中的Human),复制一个现成的比重新构建一个要简单得多。
  4. 动态获取对象运行时状态:可以保存一个对象在某一时刻的状态,并在需要时复制它。

缺点

  1. 深拷贝与浅拷贝问题:实现克隆需要仔细考虑。默认的Object.clone()方法是浅拷贝(Shallow Copy)。如果对象内部有引用其他对象(非基本类型、不可变对象如String),则需要重写clone方法实现深拷贝(Deep Copy),否则克隆对象和原对象会共享这些引用,这可能不是你想要的结果。深拷贝实现起来可能比较复杂。
  2. 对克隆方法的改造需要修改所有具体原型类的代码:如果需要在克隆过程中加入新的通用逻辑,需要修改所有原型类的clone方法。
  3. 违背“构造函数中不包含复杂逻辑”的设计原则:因为克隆不走构造函数,如果对象构建本身依赖于构造函数的复杂初始化逻辑,克隆可能会带来问题。

代码示例

建造者模式中,正常人包含头、手臂、腿、身体。这里让我们用原型模式来实现创建不同类型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):

  1. 原型作用域的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 { ... }
    
  2. ObjectFactoryProvider
    为了延迟获取原型bean(避免在注入时就固定一个实例),Spring提供了ObjectFactory或JSR-330的Provider接口。每次调用getObject()方法时,容器会返回一个新的原型bean实例。

    @Autowired
    private ObjectFactory<MyPrototypeBean> myPrototypeBeanFactory;
    
    public void doSomething() {
        MyPrototypeBean newInstance = myPrototypeBeanFactory.getObject();
        // ... 使用新的实例
    }
    

总结

原型模式提供了一种基于已有对象创建新对象的有效途径,它绕过了传统的构造方法,通过复制(克隆)来快速创建状态复杂的对象。它在需要高性能创建复杂对象、或者需要隔离客户端与具体产品类的场景下非常有用。

posted @ 2025-08-31 19:45  庞去广  阅读(10)  评论(0)    收藏  举报