代码优雅之路-设计模式之原型模式
概述
在日常开发过程当中,我们难免会遇到类与类之间转换的过程,可能这两个类字段类型,名称只存在微小区别,甚至一模一样,比如DTO到Entity过程,这个时候就需要本文的重点模式-原型模式
概念
原型模式(Prototype Pattern) 官方原文为:
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
意思大概是当我们使用原型实例要指定创建对象种类的时候,通过复制该原型创建新对象。
核心
原型模式的核心是需要拷贝原型对象,且不能通过new对象的形式getter,setter来进行拷贝,需将原型对象通过内存二进制流的形式进行拷贝,二进制流的形式无需经历new对象的构造过程,性能会些许提升。
应用场景
原型模式适用(包含但不限于)以下场景
- 体力劳动过剩
- 类初始化消耗资源过多
- 构造方法过于复杂
看到这,可能会问,体力劳动过剩是什么鬼? 大家看一篇代码示例:
public static void main(String[] args) {
OldDomain old = new OldDomain();
...省略set过程
NewDomain newDomain = new NewDomain();
newDomain.setAddress(old.getAddress());
newDomain.setAge(old.getAge());
newDomain.setName(old.getName());
newDomain.setIdCard(old.getIdCard());
newDomain.setPhone(old.getPhone());
}
疯不疯? 况且真实项目情况时,可能会有几十个,甚至嵌套了N个模型类吧,一个一个去getter,setter,即使你体力劳动过剩也不能这么玩吧。但是这种方式不能完全否定,为什么呢,文末说明。
使用
JDK为我们提供了一个原型接口Cloneable:
public interface Cloneable {
}
让我们的原型对象去实现它并重写clone()
方法;
public class NewDomain implements Cloneable {
/** 名字 */
private String name;
/** 年龄 */
private String age;
/** 地址 */
private String address;
/** 电话 */
private String phone;
/** 身份证 */
private String idCard;
@Override
protected NewDomain clone() throws CloneNotSupportedException {
return (NewDomain) super.clone();
}
}
我们创建原型对象并进行克隆操作:
public static void main(String[] args) throws CloneNotSupportedException {
NewDomain newDomain = new NewDomain();
newDomain.setAddress("中国");
newDomain.setAge("18");
newDomain.setName("法外狂徒张三");
newDomain.setIdCard("112233");
newDomain.setPhone("1100");
NewDomain cloneNewDomain = newDomain.clone();
System.out.println("第一个对象---------" + newDomain.getName());
System.out.println("克隆对象----------" + cloneNewDomain.getName());
}
我们可以得到如下输出
第一个对象---------法外狂徒张三
克隆对象----------法外狂徒张三
至此,我们发现已经克隆成功,但是这种形式只是浅克隆形式,也就是内存地址引用,原型发生变化,克隆对象也会跟着变化,如:
public static void main(String[] args) throws CloneNotSupportedException {
NewDomain newDomain = new NewDomain();
newDomain.setAddress("中国");
newDomain.setName("法外狂徒张三");
newDomain.setCars(new ArrayList<>(Arrays.asList("自行车","三轮车")));
NewDomain cloneNewDomain = newDomain.clone();
System.out.println("第一个对象---------" + newDomain);
System.out.println("克隆对象----------" + cloneNewDomain);
newDomain.getCars().add("电动车");
System.out.println("第一个对象赋值后---------" + newDomain);
System.out.println("克隆对象----------" + cloneNewDomain);
}
输出结果如下
第一个对象---------[自行车, 三轮车]
克隆对象----------[自行车, 三轮车]
第一个对象赋值后---------[自行车, 三轮车, 电动车]
克隆对象----------[自行车, 三轮车, 电动车]
你会发现,第一个对象发生改变后,被克隆对象也发生了改变,如果你的新对象不想受原型对象影响,就需要采用深克隆形式,深克隆形式有两种一种是JSON序列化,另一种则是流读取输出的形式,现在已二进制流读取
public NewDomain deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (NewDomain) objectInputStream.readObject();
}
将上面的newDomain.clone();
变更为newDomain.deepClone();
执行结果如下:
第一个对象---------[自行车, 三轮车]
克隆对象----------[自行车, 三轮车]
第一个对象赋值后---------[自行车, 三轮车, 电动车]
克隆对象----------[自行车, 三轮车]
你会发现克隆对象不再受原型对象的改变而改变。
结言
原型模式的优点就是性能还不错,毕竟是基于二进制流,比直接new对象性能上要提升许多,缺点也很明显,如果想实现深克隆,势必要每个类都需书写深克隆方法,在对象嵌套过程当中,实现过程势必繁琐。但是我们在工作当中,深克隆的方式避免不了,而且我们在工作中往往不去在每个对象当中去书写深克隆方法,为了方便,我们可能通过反射来获取,但这些深克隆方式势必会影响性能,相比之下,getter,setter的效率反而比较高。所以原型模式为我们提供了一种理念,我们可以根据实际需求去运用。