一 原型模式引入
原型模式作为创立型模式的最初一种,它并没有波及到很多的内容,咱们来看一下
首先举一个生存上的例子,例如咱们要出版一本书,其中有一些信息字段,例如书名价格等等
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 等
}
(一) 间接 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 克隆
(二) 浅克隆
用到克隆,首先就对 Book 类进行解决
- 首先实现 Cloneable 接口
- 接着重写 clone 办法
public class Book implements Cloneable{
private String name; // 姓名
private int price; // 价格
private Partner partner; // 合作伙伴
@Override
protected 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 克隆 A
Book 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 克隆 A
Book 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 对象是援用同一个对象的
(三) 深克隆
如何解决下面的问题呢,咱们须要从新重写 clone 的内容,同时在援用类型中也实现浅克隆
(1) 被援用类型实现浅克隆
全代码如下
public class Partner implements Cloneable {
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {return super.clone();
}
// 省略构造函数、get set、toString 等
}
(2) 批改援用类 cloen 办法
public class Book implements Cloneable{
private String name; // 姓名
private int price; // 价格
private Partner partner; // 合作伙伴
@Override
protected 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 克隆 A
Book 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 中 合作伙伴 的值,没有受到影响,这也就是咱们一结尾想要实现的成果了
<div align=”center”>
<img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e0ceb7c9c9f147d58b48cc48f30661f6~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%">
</div>
二 原型模式实践
(一) 什么是原型模式
在一些程序中,或者须要创立大量雷同或者类似的对象,在构造函数的执行比拟迟缓的时候,屡次通过传统的构造函数创建对象,就会简单且耗资源,同时创立时的细节也一样裸露了进去
原型模式就能够帮忙咱们解决这一问题
定义:原型模式,甩原型实例指定创建对象的品种,并且通过拷贝这些原型创立新的对象
(二) 构造
依据结构图简略说一下其中的角色:
- 形象原型类(Prototype):规定了具体原型对象必须实现的接口
- 具体原型类(ConcretePrototype):实现 clone 办法,即一个克隆本身的操作
- 拜访类(Client):应用具体原型类中的 clone 办法克隆本身,从而创立一个新的对象
(三) 两种模式
依据下面的例子也能够看进去了,原型模式分为浅克隆和深克隆
- 浅克隆:创立一个新对象,新对象的属性和原来对象完全相同,对于非根本类型属性,仍指向原有属性所指向的对象的内存地址
- 深克隆:创立一个新对象,属性中援用的其余对象也会被克隆,不再指向原有对象地址。
(四) 优缺点
长处:
- Java 原型模式基于内存二进制流复制,比间接 new 的性能会更好一些
- 能够利用深克隆保留对象状态,存一份旧的(克隆进去),在对其批改,能够充当一个撤销性能
毛病:
- 须要配置 clone 办法,革新时须要对已有类进行批改,违反“开闭准则”
-
如果对象间存在多重嵌套援用时,每一层都须要实现克隆
- 例如上例中,Book 中实现深克隆,Partner 中实现浅克隆,所以要留神深浅克隆使用切当