原型模式

# 原型模式 > 原型模式(Prototype Pattern)指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创 >建型模式; > >原型模式的核心在于复制原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行复制,不需要 >再精力耗时的对象初始化过程(不调用构造函数),性能提升很多。当对象的构造过程比较耗时时,可以把当前系统 >已存在的对象作为原型,对其进行复制(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大缩短; ## 原型模式类图 ![](https://oscimg.oschina.net/oscnet/up-85e3522fe29b05e31c4957bbcdf82eeba4c.png) ### IPrototype 定义克隆的方法 类似于JDK自带的Cloneable ```text public interface IPrototype<T> { T clone(); } ``` ### ConcretePrototype 具体的要克隆的对象 ```text 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; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + '}'; } } ``` ### Client 客户端 测试 ```text public static void main(String[] args) { //创建原型对象 ConcretePrototype prototype = new ConcretePrototype(); prototype.setAge(18); prototype.setName("Tom"); System.out.println(prototype); //拷贝原型对象 ConcretePrototype cloneType = prototype.clone(); System.out.println(cloneType); System.err.println(cloneType == prototype); } ``` 运行结果: ```text ConcretePrototype{age=18, name='Tom'} ConcretePrototype{age=18, name='Tom'} false ``` ### 实现JDK Cloneable的克隆对象写法 ```text public class ConcretePrototype1 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 ConcretePrototype1 clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); System.err.println("Clone Error!"); } return (ConcretePrototype1) clone; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + '}'; } } ``` 输出结果同上;以上的两个属性都是基本数据类型和String,并没有引用类型,下面我们添加一个引用类型的属性测试以下👇👇 ### ConcretePrototype添加一个List类型属性 ```text public class ConcretePrototype implements IPrototype { private int age; private String name; private List<String> hobbies; public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } 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); concretePrototype.setHobbies(this.hobbies); return concretePrototype; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } } ``` ### SETTER方法实现浅克隆 ![](https://oscimg.oschina.net/oscnet/up-569ebf8ef5519ace6b434b1301d87165f2c.png) > 浅克隆也叫浅拷贝:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 ```text public static void main(String[] args) { //创建原型对象 ConcretePrototype1 prototype = new ConcretePrototype1(); prototype.setAge(18); prototype.setName("Tom"); ArrayList<String> strings = Lists.newArrayList("数学", "英语"); prototype.setHobbies(strings); System.out.println(prototype); //拷贝原型对象 ConcretePrototype1 cloneType = prototype.clone(); cloneType.getHobbies().add("语文"); System.out.println(cloneType); System.out.println(prototype); System.err.println(cloneType == prototype); System.err.println(cloneType.getHobbies() == prototype.getHobbies()); } 输出结果: ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语]} ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语, 语文]} ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语, 语文]} false // 这里为什么结果是true看到上面画的图应该可以看懂吧,set方法其实是将list的引用设置过去,并不是创建一个新的list再赋值! true ``` ## 通过内存字节流"克隆"对象,实现深克隆 ![](https://oscimg.oschina.net/oscnet/up-41df4d66a80f2d0c2bfd87b7fea52ca9ddd.png) > 深克隆也叫深拷贝:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 ```text public class ConcretePrototypeDeep implements Cloneable,Serializable { private int age; private String name; private List<String> hobbies; public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } 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 ConcretePrototypeDeep deepClone() { try { //将当前对象信息写到内存 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); //从内存中读取对象信息,强制转换成当前类型 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (ConcretePrototypeDeep) objectInputStream.readObject(); } catch (IOException ioException) { ioException.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } } ``` 输出结果: ```text ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语]} ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语, 语文]} ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语]} false false ``` 在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。 > 要实现深拷贝,必须实现Cloneable接口,去重写clone方法,否则会抛出CloneNotSupportedException异常, >但是如果对象中包含很多引用类型的属性,这样去覆盖clone方法其实是很麻烦的,可以优先使用序列化的方式实现! ## 克隆破坏单例模式 如果我们克隆的目标的对象是单例对象,这便意味着,深克隆会破坏单例。解决以上问题的思路: - **禁止深克隆** - **在单例对象的getInstance方法,返回当前对象,而不是去新创建一个对象或者通过内存字节流等方法生成对象** ## 原型模式在Java中的应用 ArrayList底层是基于数组结果的,它的动态扩容过程是创建一个新的数组,并把数组中的元素拷贝过去,用新的数组来继续存放元素; 在创建新数组的过程中便使用了原型模式。 ### java.util.ArrayList.clone ```text 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); } } ``` ### java.util.Arrays.copyOf(T[], int) ```text public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } ``` ### java.util.Arrays.copyOf(U[], int, java.lang.Class<? extends T[]>) ```text public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } ``` 最终还是System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));这个方法来完成数组的拷贝! 像我们使用的com.alibaba.fastjson.JSON#parseObject(java.lang.String, java.lang.Class<T>, com.alibaba.fastjson.parser.Feature...) ,org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)等都是原型模式; ## 原型模式适用场景 - **类初始化消耗资源过多** - **new一个对象需要很多繁琐的过程(数据准备,访问权限等)** - **构造函数比较复杂** - **循环体内产生大量对象时** ## 原型模式的优点 - **Java自带的原型模式是基于内存二进制流的复制,在性能上比直接创建一个对象更加优良** - **可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程, 以便在需要的时候使用,可辅助实现撤销操作。** ## 原型模式的缺点 - **需要为每一个类都配置一个 clone 方法** - **clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则** - **当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆, 实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当** > 参考资料 > [原型模式(原型设计模式)详解](http://c.biancheng.net/view/1343.html)