1. 原型模式引入
原型模式作为创建型模式的最后一种,它并没有涉及到很多的内容,我们来看一下
首先举一个生活上的例子,例如我们要出版一本书,其中有一些信息字段,例如书名价格等等
public class Book {private String name; // 姓名private int price; // 价格private Partner partner; // 合作伙伴// 省略构造函数、get set、toString 等}
引用类型 Partner 也很简单
public class Partner{private String name;// 省略构造函数、get set、toString 等}
1.1 直接 new
书籍出版肯定不能只出一本,如何大批量生产呢?
有人或许想到,像下面这样每一次都重新 new(甚至写个 for 循环),咱先不说大量 new 的效率问题,首先每次一都需要重新给新对象复制,这不就像,我每刊印一本书,就得重新写一次吗???
public class Test {public static void main(String[] args) throws CloneNotSupportedException {// 初始化一个合作伙伴类型Partner partner = new Partner("张三");// 带参赋值Book bookA = new Book("理想二旬不止", 66, partner);Book bookB = new Book("理想二旬不止", 66, partner);System.out.println("A: " + bookA.toString());System.out.println("A: " + bookA.hashCode());System.out.println("B: " + bookB.toString());System.out.println("B: " + bookB.hashCode());}}
有的同学还或许想到了,先把 A 创建出来,然后再赋值给 B、C ….. 等等,但是这种方式其实是传递引用而不是传值,这就好比在 C 和 B 上写着,内容详情请看 A
public class Test {public static void main(String[] args) throws CloneNotSupportedException {// 初始化一个合作伙伴类型Partner partner = new Partner("张三");// 带参赋值Book bookA = new Book("理想二旬不止", 66, partner);System.out.println("A: " + bookA.toString());System.out.println("A: " + bookA.hashCode());// 引用赋值Book bookB = bookA;System.out.println("B: " + bookB.toString());System.out.println("B: " + bookB.hashCode());}}
这两样显然是不行的,我们正常的思路是,作者只需要写一次书籍内容,先刊印一本,如果能行,就照着这个样本进行大批量同彩复印,而上面的传引用方法也显然不合适,这就需要用到Java克隆
1.2 浅克隆
用到克隆,首先就对 Book 类进行处理
- 首先实现 Cloneable 接口
接着重写 clone 方法
public class Book implements Cloneable{private String name; // 姓名private int price; // 价格private Partner partner; // 合作伙伴@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}// 省略构造函数、get set、toString 等}
再来测试一下
public class Test {public static void main(String[] args) throws CloneNotSupportedException {// 初始化一个合作伙伴类型Partner partner = new Partner("张三");// 带参赋值Book bookA = new Book("理想二旬不止", 66, partner);// B 克隆 ABook bookB = (Book) bookA.clone();System.out.println("A: " + bookA.toString());System.out.println("A: " + bookA.hashCode());System.out.println("B: " + bookB.toString());System.out.println("B: " + bookB.hashCode());}}
执行结果
A: Book{name=’理想二旬不止’, price=66, partner=Partner{name=张三}}
A: 460141958
B: Book{name=’理想二旬不止’, price=66, partner=Partner{name=张三}}
B: 1163157884
结果非常明显,书籍信息是一致的,但是内存地址是不一样的,也就是说确实克隆成功了,打印其 hashCode 发现两者并不相同,说明不止指向同一个,也是满足我们要求的
到这里并没有结束,你会发现还是有问题,当你刊印的过程中修改一些值的内容的时候,你看看效果
public class Test {public static void main(String[] args) throws CloneNotSupportedException {// 初始化一个合作伙伴类型Partner partner = new Partner("张三");// 带参赋值Book bookA = new Book("理想二旬不止", 66, partner);// B 克隆 ABook bookB = (Book) bookA.clone();// 修改数据partner.setName("李四");System.out.println("A: " + bookA.toString());System.out.println("A: " + bookA.hashCode());System.out.println("B: " + bookB.toString());System.out.println("B: " + bookB.hashCode());}}
执行结果
A: Book{name=’理想二旬不止’, price=66, partner=Partner{name=李四}}
A: 460141958
B: Book{name=’理想二旬不止’, price=66, partner=Partner{name=李四}}
B: 1163157884
???这不对啊,B 明明是先克隆 A 的,为什么我在克隆后,修改了 A 中一个的值,但是B也变化了啊
这就是典型的浅克隆,在 Book 类,当字段是引用类型,例如 Partner 这个合作伙伴类,就是我们自定义的类,这种情况复制引用不赋值引用的对象,因此,原始对象和复制后的这个Partner对象是引用同一个对象的
1.3 深克隆
如何解决上面的问题呢,我们需要重新重写 clone 的内容,同时在引用类型中也实现浅克隆
1.3.1 被引用类型实现浅克隆
全代码如下
public class Partner implements Cloneable {private String name;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}// 省略构造函数、get set、toString 等}
1.3.2 修改引用类 cloen 方法
public class Book implements Cloneable{private String name; // 姓名private int price; // 价格private Partner partner; // 合作伙伴@Overrideprotected Object clone() throws CloneNotSupportedException {Object clone = super.clone();Book book = (Book) clone;book.partner =(Partner) this.partner.clone();return clone;}// 省略构造函数、get set、toString 等}
测试一下
public class Test {public static void main(String[] args) throws CloneNotSupportedException {// 初始化一个合作伙伴类型Partner partner = new Partner("张三");// 带参赋值Book bookA = new Book("理想二旬不止", 66, partner);// B 克隆 ABook bookB = (Book) bookA.clone();// 修改数据partner.setName("李四");System.out.println("A: " + bookA.toString());System.out.println("A: " + bookA.hashCode());System.out.println("B: " + bookB.toString());System.out.println("B: " + bookB.hashCode());}}
执行效果
A: Book{name=’理想二旬不止’, price=66, partner=Partner{name=李四}}
A: 460141958
B: Book{name=’理想二旬不止’, price=66, partner=Partner{name=张三}}
B: 1163157884
可以看到,B 克隆 A 后,修改 A 中 合作伙伴 的值,没有受到影响,这也就是我们一开头想要实现的效果了
2. 原型模式理论
2.1 什么是原型模式
在一些程序中,或许需要创建大量相同或者相似的对象,在构造函数的执行比较缓慢的时候,多次通过传统的构造函数创建对象,就会复杂且耗资源,同时创建时的细节也一样暴露了出来
原型模式就可以帮助我们解决这一问题
定义:原型模式,甩原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
2.2 结构

根据结构图简单说一下其中的角色:
- 抽象原型类(Prototype):规定了具体原型对象必须实现的接口
- 具体原型类(ConcretePrototype):实现 clone 方法,即一个克隆自身的操作
- 访问类(Client):使用具体原型类中的 clone 方法克隆自身,从而创建一个新的对象
2.3 两种模式
根据上面的例子也可以看出来了,原型模式分为浅克隆和深克隆
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
2.4 优缺点
优点:
Java 原型模式基于内存二进制流复制,比直接 new 的性能会更好一些
- 可以利用深克隆保存对象状态,存一份旧的(克隆出来),在对其修改,可以充当一个撤销功能
缺点:
- 需要配置 clone 方法,改造时需要对已有类进行修改,违背 “开闭原则”
- 如果对象间存在多重嵌套引用时,每一层都需要实现克隆
- 例如上例中,Book 中实现深克隆,Partner 中实现浅克隆,所以要注意深浅克隆运用得当
