尺有所短,寸有所长;不忘初心,方得始终。
一、原型模式是什么
原型模式是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。统一个类,新的对象
通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式是比较好的实现方式。
原型模式我们也称为克隆模式**,即某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。类似于细胞的有丝分裂
原型模式的克隆方式有两种:
-
浅克隆
只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址。
-
深度克隆
深复制把要复制的对象所引用的对象都复制了一遍
二、原型模式的适用场景
一般用于创建大对象,或初始化繁琐的对象。比如游戏背景,地图,画布等等,Java 中的 Object clone() 方法就是使用的原型模式
以下场景适用:
-
性能和安全要求高的场景,可使用原型模式;
-
类初始化需要消化非常多的资源,比如数据资源、硬件资源等,可使用原型模式;
-
通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,可使用原型模式;
-
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以使用原型模式拷贝多个对象供调用者使用。
在实际应用中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone的方法创建一个对象,然后由工厂方法提供给调用者。
三、原型模式结构
-
原型 (Prototype) 接口将对克隆方法进行声明。一般情况下具体原型直接使用Cloneable接口既可。
.NET在System命名空间中提供了Cloneable接口,其中它提供唯一的方法Clone(),只需要实现这个接口就可以完成原型模式了。由于它直接操作内存中的二进制流,当大量操作或操作复杂对象时,性能优势将会很明显。
-
具体原型 (Concrete Prototype):类将实现克隆方法。
-
客户端 :可以复制实现了原型接口的任何对象。
四、原型模式实现方式
原型模式实现方式比较简单,只需要在原型类中实现Cloneable接口,重写clone()方法既可。
五、原型模式的两种实现
5.1 浅克隆
被克隆对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址
-
原型类
public class User implements Cloneable{
private String name;
private Role role;
private int age;
// get set 方法省略
/**
* 实现克隆的方法
*/
@Override
public Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
return clone;
}
}
public class Role implements Cloneable{
private String roleId;
private String roleName;
public Role(String roleId, String roleName) {
this.roleId = roleId;
this.roleName = roleName;
}
// get set 方法省略
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
} -
客户端
User user = new User();
user.setName("张三");
user.setAge(18);
Role role = new Role("1", "超级管理员");
user.setRole(role);
System.out.println("---------输出原型对象的属性-----------");
System.out.println("----原型user对象======>>>>>>>> :" + user);
System.out.println("----原型role对象======>>>>>>>> :" + role);
System.out.println("----原型user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user));
// 克隆对象
User user1 =(User) user.clone();
// 修改原型对象中的属性
role.setRoleName("普调管理员");
System.out.println("----修改后的原型user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user));
// 修改参数
user1.setName("李四");
System.out.println("------------克隆对象的属性----------");
System.out.println("----克隆的user对象======>>>>>>>> :" + user1);
System.out.println("----克隆的role对象======>>>>>>>> :" + user1.getRole());
System.out.println("----克隆user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user1)); -
结果剖析
-
从输出结果中可以看出原型User对象生成了新的克隆对象
-
User的引用对象Role是同一个对象
5.2 深度克隆
被复制对象中,除去那些引用其他对象的变量之外的所有变量都含有与原来的对象相同的值。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
深度克隆(deep clone)有两种实现方式,
-
第一种是通过浅克隆实现深度克隆
-
第二种是通过序列化和反序列化实现深度克隆
5.2.1 通过浅克隆实现深度克隆
改造浅克隆中User的clone方法既可
-
原型类
public class User implements Cloneable{
private String name;
private Role role;
private int age;
// get set 方法省略
/**
* 实现克隆的方法
*/
@Override
public Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
return clone;
}
}
public class Role implements Cloneable{
private String roleId;
private String roleName;
public Role(String roleId, String roleName) {
this.roleId = roleId;
this.roleName = roleName;
}
// get set 方法省略
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
// 实现深度克隆(deep clone)
User user = (User)clone;
user.role = (Role) this.role.clone();
}
} -
客户端:代码与浅克隆代码一致
5.2.2 通过序列化和反序列化实现深度克隆
原型类实现Serializable接口,在客户端中使用序列化和反序列化克隆对象既可。
-
原型类
public class User implements Serializable{
private String name;
private Role role;
private int age;
// get set 方法省略
}
public class Role implements Serializable{
private String roleId;
private String roleName;
public Role(String roleId, String roleName) {
this.roleId = roleId;
this.roleName = roleName;
}
// get set 方法省略
} -
客户端
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("张三");
user.setAge(18);
Role role = new Role("1", "超级管理员");
user.setRole(role);
System.out.println("---------输出原型对象的属性-----------");
System.out.println("----原型user对象======>>>>>>>> :" + user);
System.out.println("----原型role对象======>>>>>>>> :" + role);
System.out.println("----原型user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user));
//使用序列化和反序列化实现深复制
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
//使用序列化和反序列化实现深复制
// 克隆对象
User user1 = (User) ois.readObject();
// 修改原型对象中的属性
role.setRoleName("普调管理员");
System.out.println("----修改后的原型user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user));
// 修改参数
user1.setName("李四");
System.out.println("------------克隆对象的属性----------");
System.out.println("----克隆的user对象======>>>>>>>> :" + user1);
System.out.println("----克隆的role对象======>>>>>>>> :" + user1.getRole());
System.out.println("----克隆user对象的属性值======>>>>>>>> :" + JSONObject.toJSON(user1));
}
5.2.3 深度克隆结果剖析
上述两种深度克隆输出相同的结果
-
从输出结果中可以看出原型User对象生成了新的克隆对象
-
User的引用对象Role也生成了新的克隆对象由于在一些特定场合,会经常涉及深复制和浅复制,比如说,数据集对象DataSet中的Clone()方法和Copy()方法
-
Clone()方法用来复制DataSet的结构,但不复制DataSet的数据,实现了原型模式的浅复制
-
Copy()方法不但复制结构,还复制数据,其实就是实现了原型模式的深复制
六、原型模式的优缺点
-
优点
-
克隆对象无需与它们所属的具体类相耦合。 -
克隆预生成原型, 避免反复运行初始化代码。 -
可以更方便地生成复杂对象。 -
可以用继承以外的方式来处理复杂对象的不同配置。 -
缺点
当对象存在比较复杂的循环引用时,原型模式会很复杂。
七、原型模式和其他模式的区别
-
较为简单, 而且可以更方便地通过子类进行定制的对象使用工厂方法模式,更灵活但更加复杂使用抽象工厂模式、 原型模式或生成器模式。 -
原型模式可用于保存命令模式的历史记录。 -
大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。你可以通过该模式来复制复杂结构, 而非从零开始重新构造。 -
原型模式并不基于继承, 因此没有不可以多继承的缺点,但是原型需要对被复制对象进行初始化。工厂方法基于继承, 但是它不需要初始化步骤。
八、总结
原型模式通过Object的clone()方法实现,由于是内存操作,无视构造方法和访问权限,直接获取新的对象。但对于引用类型,需使用深拷贝,其它浅拷贝即可。
原文始发于微信公众号(星河之码):设计模式(5):原型模式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27193.html