设计模式04-原型模式
学习目标:
》原型模式的应用场景
》原型模式之深克隆与浅克隆
》了解克隆是如何破坏单例的
》原型模式的优缺点
》掌握建造者模式和工厂模式的区别
知识前提:
》了解并掌握工厂模式
》已掌握单例模式
》听说过模式,但想要深入学习
1.1.原型模式的定义
Prototype Pattern,是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
调用者不需要知道任何创建细节,不调用构造函数。
属于创建型模式。
原型模式其实在开发中很常见。比如:我们对一个对象设值时,通常的做法:
先new 一个实例,然后调用set方法,传参另一个对象【原型】,调用get方法进行设值,如下所示代码:
package com.wf.prototype; import lombok.Data; /** * @ClassName ExamPaper * @Description 考试试卷 * @Author wf * @Date 2020/5/7 12:49 * @Version 1.0 */ @Data public class ExamPaper { private Long id; private String name; public ExamPaper copy (){ ExamPaper examPaper = new ExamPaper(); examPaper.setId(this.getId()); examPaper.setName(this.getName()); //.... return examPaper; } }
注意:
这里使用lombok组件,依赖如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
这里copy方法,就用到的原型模式。【原来的对象,生成另一个对象】
但是,它有一些问题:
》代码使用大量get,set转换。重复度很高。
》代码不够优雅。
》其实是一种硬编码形式,当增加一个字段时,就必须得修改copy方法。
为了改善上面的问题,基于反射,提取bean转换工具类:
1.1.1.beanUtil工具类
package com.wf.prototype; import java.lang.reflect.Field; /** * @ClassName BeanUtils * @Description 对象转换工具类 * @Author wf * @Date 2020/5/7 12:51 * @Version 1.0 */ public class BeanUtils { public static Object copy(Object protoType){ Class<?> clazz = protoType.getClass(); Object returnValue = null; try { returnValue = clazz.newInstance(); for (Field field: clazz.getDeclaredFields()){ field.setAccessible(true); //根据原型.get,设置给set //通过反射调用,returnValue.setXXx(proto.getXXx()) field.set(returnValue,field.get(protoType)); } } catch (Exception e) { e.printStackTrace(); } return returnValue; } }
说明:
上面的工具类,使用了反射进行设值,就是一种原型模式的处理。它的本质和set设值是一样的,不过,代码更为优雅。
1.2.原型模式的代码示例
1.2.1.使用原始的硬编码式实现原型对象创建
1.2.1.1.顶层接口
package com.wf.prototype.general; /** * @ClassName IProtoType * @Description 原型实例的顶层接口 * @Author wf * @Date 2020/5/12 13:59 * @Version 1.0 */ public interface IProtoType<T> { T clone(); }
1.2.1.2.原型接口实现
package com.wf.prototype.general; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ public class ConcretePrototype implements IProtoType { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public ConcretePrototype clone() { ConcretePrototype concretePrototype = new ConcretePrototype(); concretePrototype.setAge(this.age); concretePrototype.setName(this.name); return concretePrototype; } }
说明:
实现类中,定义clone方法,采用new的方式,然后硬编码set值。产生新的原型实例。
1.2.1.3.客户端代码调用
package com.wf.prototype.general; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); System.out.println(prototype); ConcretePrototype cloneType = prototype.clone(); System.out.println(cloneType); } }
运行结果如下:

说明:
这里通过调用clone方法,得到一个新的实例。并且成员的值,和原有对象是一样。
我们在编码中,经常存在对象转换,如:DO转换DTO,或VO。我们需要把成员的值,也一起设置给新的对象。
就可以使用原型模式。
1.2.2.原型模式的适用场景总结
1.类的初始化消耗资源较多【需要调用很多set方法】
2.new 产生的一个对象需要非常繁琐的过程(数据准备,访问权限等)
3.构造函数比较复杂
4.循环体中生产大量对象时
1.2.3.浅克隆与深克隆
1.2.3.1.浅克隆
Jdk本身已经有了浅克隆的方法实现。通过实现Cloneable接口来实现:
注意:
通过查询Cloneable接口的源码,发现内部并没有定义clone方法,而是使用Object中clone方法。
Object是所有类的父类,所以,clone方法定义在Object中,是一个native方法:
protected native Object clone() throws CloneNotSupportedException;
1.代码示例
package com.wf.prototype.shallow; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现,基于jdk的实现 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ public class ConcretePrototype implements Cloneable { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public ConcretePrototype clone() { try { //调用jdk中clone()实现 return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
测试类如下:
package com.wf.prototype.shallow;/** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); System.out.println(prototype); ConcretePrototype cloneType = prototype.clone(); System.out.println(cloneType); } }
测试结果如下:

2.浅克隆的问题探究
根据上面的测试结果,似乎和自定义clone接口,没有什么不同。
问题是什么呢?再来看下面的代码:
package com.wf.prototype.shallow; import java.util.ArrayList; import java.util.List; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现,基于jdk的实现 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ public class ConcretePrototype implements Cloneable { private int age; private String name; //增加一个引用类型成员 private List<String> hobbies = new ArrayList<>(); public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } @Override public ConcretePrototype clone() { try { //调用jdk中clone()实现 return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
测试类如下:
package com.wf.prototype.shallow; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); System.out.println(prototype); ConcretePrototype cloneType = prototype.clone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println(cloneType); } }
测试结果如下:

似乎是正确的。再修改测试代码:
package com.wf.prototype.shallow; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.clone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println(prototype); System.out.println(cloneType); } }
测试结果,大出所料,如下:

说明:
根据我们的编程意图,我们只是希望修改克隆对象的成员值,而不能改变,原有对象的属性值。
但是,现在原有对象属性也被修改了,显然是有问题的。
为什么会出现这个问题呢?
猜测:可能原型对象和克隆对象,两个实现指向同一个实例,即它们的内存地址是一样的。
代码验证如下:
package com.wf.prototype.shallow; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.clone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println("原型对象:"+prototype); System.out.println("克隆对象:"+cloneType); System.out.println(prototype==cloneType);//预期:返回true } }
测试结果如下:

总结:
显然,上面的猜测是错误的。两个实例是不同的对象,指向不同的内存地址。
那到底是什么原因呢?再次代码调试:
package com.wf.prototype.shallow; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.clone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println("原型对象:"+prototype); System.out.println("克隆对象:"+cloneType); System.out.println(prototype==cloneType);//预期:返回true,实际为false System.out.println("原型对象的爱好属性:"+prototype.getHobbies()); System.out.println("克隆对象的爱好属性:"+cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); } }
测试结果如下:

总结:
真相浮出水面。我们发现两个对象中成员hobbies的内存地址是一样。
我们发现,hobbies是一个List类型成员,是引用类型。jdk中clone方法,在克隆对象时,只是把原型对象成员的内存地址
进行的复制,而不是复制属性的值。
所以,当修改克隆对象属性值,原型对象的属性值也会被改变。
这显然是不符合代码预期的,是存在代码风险的。我们需要的是克隆出一个新的对象,不会影响或修改原型对象。
那么,怎么解决这个问题呢?提出深克隆的解决方案。
浅克隆的问题:
主要是针对原型对象中,存在引用类型成员时,产生的数据安全问题。
1.2.3.2.深克隆
如何实现深克隆呢?方案是什么?
通过序列化与反序列化来实现。
1.代码实现
首先,需要实现Serializable接口。
package com.wf.prototype.deep; import java.io.*; import java.util.ArrayList; import java.util.List; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现,基于序列化实现深克隆 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; //增加一个引用类型成员 private List<String> hobbies = new ArrayList<>(); public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } /** * 基于jdk实现浅克隆 * @return */ @Override public ConcretePrototype clone() { try { //调用jdk中clone()实现 return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } /** * 基于序列化与反序列化,实现深克隆 * @return */ public ConcretePrototype deepClone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (ConcretePrototype) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
测试类如下:
package com.wf.prototype.deep; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.deepClone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println("原型对象:"+prototype); System.out.println("克隆对象:"+cloneType); System.out.println(prototype==cloneType);//预期:返回true,实际为false System.out.println("原型对象的爱好属性:"+prototype.getHobbies()); System.out.println("克隆对象的爱好属性:"+cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); } }
测试结果如下:

总结:
这种方式,有以下缺点:
》性能不好
》占用IO
2.深克隆实现方案2-----Json转换
CustomerDO customerDO= JSONObject.parseObject(JSONObject.toJSONString(customerDTO),CustomerDO.class);
1.3.原型模式与单例模式
1.3.1.浅克隆破坏单例
如果一个原型对象,本身就是一个单例类,这时提供clone方法,会破坏单例。
1.3.1.1.示例代码
package com.wf.prototype.singleton; import lombok.Data; import java.io.*; import java.util.ArrayList; import java.util.List; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现,基于序列化实现深克隆 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ @Data public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; //增加一个引用类型成员 private List<String> hobbies = new ArrayList<>(); private static ConcretePrototype instance = new ConcretePrototype(); private ConcretePrototype(){} public static ConcretePrototype getInstance(){ return instance; } /** * 基于jdk实现浅克隆 * @return */ @Override public ConcretePrototype clone() { try { //调用jdk中clone()实现 return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } /** * 基于序列化与反序列化,实现深克隆 * @return */ /* public ConcretePrototype deepClone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (ConcretePrototype) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; }*/ @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
测试代码如下:
package com.wf.prototype.singleton; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = ConcretePrototype.getInstance(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.clone(); //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println("原型对象:"+prototype); System.out.println("克隆对象:"+cloneType); System.out.println(prototype==cloneType);//预期:返回true,实际为false System.out.println("原型对象的爱好属性:"+prototype.getHobbies()); System.out.println("克隆对象的爱好属性:"+cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); } }
测试结果如下:

说明:
通过全局访问getInstance方法,获取到原生对象。
再通过clone方法,得到新的克隆对象。
显然,得到的是两个不同的实例,单例特性被破坏。
这个问题怎么解决呢?
》明确单例与原型本身就是对立的。如果要保证是单例类,就不能是原型类,不能实现clone方法。
即不实现Cloneable接口,再调用clone方法就会报错。
》可以实现cloneable接口,但clone方法只能返回一个实例。如下所示:
@Data public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; //增加一个引用类型成员 private List<String> hobbies = new ArrayList<>(); private static ConcretePrototype instance = new ConcretePrototype(); private ConcretePrototype(){} public static ConcretePrototype getInstance(){ return instance; } /** * 基于jdk实现浅克隆 * @return */ @Override public ConcretePrototype clone() { return instance; } }
但是,这没有意义,因为原型根本没有意义。
所以,单例与原型本身就是对立的。不能同时兼顾。单例和原型,只能设计其一。
1.4.原型模式的源码应用
1.4.1.Jdk源码原型实现
1.4.1.1.ArrayList类
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList去实现Cloneable接口,再查看clone方法实现:
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
1.4.1.2.HashMap类实现Cloneable
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
clone方法实现如下:
@Override public Object clone() { HashMap<K,V> result; try { result = (HashMap<K,V>)super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } result.reinitialize(); result.putMapEntries(this, false); return result; }
1.4.2.基于ArrayList实现Cloneable,实现深克隆应用
package com.wf.prototype.deep; import java.io.*; import java.util.ArrayList; import java.util.List; /** * @ClassName ConcretePrototype * @Description 具体的原型类实现,基于序列化实现深克隆 * @Author wf * @Date 2020/5/12 14:00 * @Version 1.0 */ public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; //增加一个引用类型成员 private List<String> hobbies = new ArrayList<>(); public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } /** * 基于jdk实现浅克隆 * @return */ @Override public ConcretePrototype clone() { try { //调用jdk中clone()实现 return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } /** * 基于序列化与反序列化,实现深克隆 * @return */ public ConcretePrototype deepClone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (ConcretePrototype) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 基于ArrayList底层实现clone,实现深克隆 * @return */ public ConcretePrototype deepCloneHobbies() { try { ConcretePrototype res = (ConcretePrototype) super.clone(); res.hobbies = (List)((ArrayList)res.hobbies).clone(); return res; } catch (Exception e) { e.printStackTrace(); } return null; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
测试类:
package com.wf.prototype.deep; import java.util.ArrayList; import java.util.List; /** * @ClassName Client * @Description TODO * @Author wf * @Date 2020/5/12 14:09 * @Version 1.0 */ public class Client { public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setName("test"); prototype.setAge(18); //既爱书法,也爱美术 List<String> hobbies = new ArrayList<>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //System.out.println(prototype); //调整打印顺序 ConcretePrototype cloneType = prototype.deepCloneHobbies();//调用clone方法 //还是个技术控 cloneType.getHobbies().add("技术控"); System.out.println("原型对象:"+prototype); System.out.println("克隆对象:"+cloneType); System.out.println(prototype==cloneType);//预期:返回true,实际为false System.out.println("原型对象的爱好属性:"+prototype.getHobbies()); System.out.println("克隆对象的爱好属性:"+cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); } }
测试结果如下:

说明:
上面的示例代码,虽然可以实现深克隆,但是一种硬编码方式。是基于ArrayList的特性而设计。
在实际编码中使用并不常见,这里只是对原型模式源码应用的加深理解。
1.5.原型模式的总结
1.5.1.实现深克隆的方案
1.序列化方案
2.转Json
原型模式,在实际编码中,很少使用到。原因如下:
1.复制对象
apache提供了工具类,不需要自己去实现
spring中也有实现
jdk实现的浅克隆。
学习原型模式,是为了理解其中的底层原理,当代码出现问题时,可以分析其中的问题。
同样,学习设计模式,并不是一定要套用,也可以作为加深理解,提供更多选择。
1.5.2.原型模式的优点
性能优良,jdk底层有实现原型模式,是基于内存二进制流的拷贝,比直接new一个对象性能提升更多。
可以使用深克隆方式,保存对象的状态。得到新的实例,并且成员属性值省去大量设值操作。代码更优雅。
1.5.3.原型模式的缺点
必须要配备克隆方法
当对原有对象进行改造时,需要修改代码,违反开闭原则。
深克隆,浅克隆要区分使用场景。
原型模式的应用场景
》原型模式之深克隆与浅克隆
》了解克隆是如何破坏单例的
》原型模式的优缺点
》掌握建造者模式和工厂模式的区别
浙公网安备 33010602011771号