设计模式之原型模式
设计模式中的原型模式(Prototype Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式,即直接复制现有的对象,而不是通过实例化类来创建新对象。这种模式在需要快速生成大量相似对象时非常有用,尤其是当对象的创建成本较高(如资源消耗大、初始化时间长等)时。下面是对原型模式的详细介绍:
一、定义
原型模式是指通过原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。在原型模式中,客户端可以直接从原型对象通过克隆来创建新的对象,而无需了解对象创建的细节。
二、类结构图与角色说明
- 抽象原型(Prototype):声明克隆方法的接口,是具体原型类的公共父类,可以是抽象类也可以是接口。它规定了对象复制的接口。
- 具体原型(Concrete Prototype):实现了抽象原型类中声明的克隆方法,通过实现一个克隆自身的操作来返回自身的拷贝。
- 客户(Client):让一个原型对象克隆自身从而创建一个新的对象。客户类通过原型对象的克隆方法来获取新的对象实例。
三、工作原理
原型模式的工作原理是通过将一个原型对象传给需要创建新对象的客户端,客户端通过调用原型对象的克隆方法(通常是clone()方法)来复制该原型对象,从而得到一个新的对象实例。这个新对象与原型对象在内容上是相同的,但它们在内存中拥有不同的地址,即它们是两个独立的对象。
四、深拷贝与浅拷贝
- 浅拷贝(Shallow Copy):只复制对象本身和对象中的基本数据类型字段,而不复制对象中的引用类型字段。这意味着,如果原型对象中的字段是引用类型,那么拷贝对象和原型对象会共享这些引用类型字段指向的对象。
- 深拷贝(Deep Copy):除了复制对象本身和对象中的基本数据类型字段外,还会递归地复制对象中的引用类型字段指向的对象。这样,拷贝对象和原型对象就完全独立了,修改拷贝对象不会影响原型对象。
五、优点
- 性能优良:通过拷贝现有的对象来创建新对象,比直接实例化类来创建对象要高效得多,特别是在对象的创建成本较高时。
- 逃避构造函数约束:在某些情况下,直接通过拷贝来创建对象可以避开复杂的构造函数调用,简化对象的创建过程。
六、缺点
- 需要实现克隆方法:具体原型类需要实现克隆方法,这可能会增加类的复杂性。
- 深拷贝实现复杂:对于包含复杂引用类型的对象,实现深拷贝可能会比较复杂,需要递归地复制所有引用类型字段指向的对象。
七、应用场景
- 当需要创建大量相似对象时,可以使用原型模式来快速复制对象。
- 当对象的创建成本较高时,如对象初始化需要消耗大量资源或时间,可以使用原型模式来优化性能。
- 当需要保护性拷贝对象时,即不希望外部直接修改原始对象时,可以使用原型模式来提供对象的拷贝版本供外部使用。
八、实现例子
在Java中,实现原型模式通常涉及到实现Cloneable接口并重写clone()方法。不过,从Java 9开始,引入了java.lang.Cloneable的一个默认方法clone()的规范实现(尽管它是受保护的),但实际上Object类中的clone()方法是一个受保护的方法,它默认是抛出CloneNotSupportedException的,除非类实现了Cloneable接口。因此,即使Java 9及以后版本有了一些改进,但实现原型模式时,仍然需要显式地实现Cloneable接口并重写clone()方法。
下面是一个简单的Java原型模式示例:
// 抽象原型类 abstract class Prototype implements Cloneable { private String id; // 构造方法 public Prototype(String id) { this.id = id; } // 抽象克隆方法 public abstract Prototype clone(); // 通用方法 public void show() { System.out.println("ID: " + id); } } // 具体原型类 class ConcretePrototype extends Prototype { // 构造方法 public ConcretePrototype(String id) { super(id); } // 实现克隆方法 @Override public ConcretePrototype clone() { try { return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { // 这里不应该发生,因为我们实现了Cloneable接口 throw new InternalError(e); } } } // 客户端类 public class Client { public static void main(String[] args) { // 创建一个原型对象 Prototype original = new ConcretePrototype("123"); // 克隆原型对象 Prototype cloned = original.clone(); // 检查是否创建了新的对象实例 System.out.println("Original: " + original.hashCode()); System.out.println("Cloned: " + cloned.hashCode()); // 调用方法以验证克隆对象的行为 original.show(); cloned.show(); } }
在这个例子中,Prototype是一个抽象原型类,它实现了Cloneable接口并定义了一个抽象的clone()方法。注意,由于clone()方法在Object类中是受保护的,因此我们不能直接在Prototype类中实现它(除非我们将其声明为受保护的或包私有的,但这会限制其使用)。然而,在这个示例中,我们让clone()方法在Prototype类中保持抽象,以便在子类中实现。
ConcretePrototype是具体原型类,它实现了Prototype接口并重写了clone()方法。在clone()方法中,我们调用了super.clone()来创建当前对象的浅拷贝,并将其强制转换为ConcretePrototype类型。然后,我们处理了可能的CloneNotSupportedException,但在这个例子中,由于我们实现了Cloneable接口,所以不会抛出此异常。
最后,在Client类中,我们创建了一个ConcretePrototype对象,并使用其clone()方法创建了一个克隆对象。然后,我们验证了这两个对象是否真的是不同的实例(通过打印它们的哈希码),并调用了show()方法来验证克隆对象的行为是否与原始对象相同。读者可以试试,应该是不相同的。
九、结束语
原型模式是一种非常实用的设计模式,它通过拷贝现有对象来创建新对象,简化了对象的创建过程,并提高了性能。然而,在使用时需要注意深拷贝和浅拷贝的区别,以及实现克隆方法可能带来的复杂性。如果生产环境使用,一定要注意深浅拷贝的影响,要慎重。