赞
踩
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。
原型模式的核心在于拷贝原型对象,以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无须再经历耗时的对象创建过程(不调用构造函数),性能提升许多。当对象的创建过程比较耗时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。
你一定遇到过大篇幅的 getter、setter赋值的场景,比如这样的代码:
代码非常工整,命名非常规范,注释也写的很全面,其实这就是原型模式的需求场景。但是,大家觉得这样的代码优雅吗?我认为,这样的代码属于纯体力劳动,那原型模式能帮助我们解决这样的问题。
原型模式主要适用于一下场景:
在 Spring 中,原型模式应用的非常广泛。例如:scope = “prototype”,我们经常使用的JSON.parseObject()也是一种原型模式。
下面来看一下原型模式的写法:
/**
* 自定义接口
*/
public interface IPrototype<T> {
T clone();
}
编写实现类,并实现 clone() 方法:
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 + '\'' + '}'; } }
下面进行测试:
public class PrototypeTest {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Jack");
System.out.println(prototype);
//拷贝原型对象
ConcretePrototype cloneType = prototype.clone();
System.out.println(cloneType);
}
}
运行结果:
可以看出,结果是一样的。这个时候,有小伙伴就会问了,原型模式就这么简单吗?对,就是这么简单。在这个简单的场景之下,看上去操作好像变复杂了,但是如果有好几百个属性需要复制,那我们就可以一劳永逸。
上面的过程,是我们自己完成,其实JDK已经有写好的接口来实现原型模式了,不用我们自己来写接口再去自己实现,我们只需要实现Cloneable接口即可,来看 JDK 的实现:
@Data public class ConcretePrototype implements Cloneable { private int age; private String name; /** * 新增个人爱好属性 */ private List<String> hobbies; @Override public ConcretePrototype clone() { try { return (ConcretePrototype)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
测试代码:
public class PrototypeTest { public static void main(String[] args) { //创建原型对象 ConcretePrototype prototype = new ConcretePrototype(); prototype.setAge(18); prototype.setName("Jack"); List<String> hobbies = new ArrayList<>(); hobbies.add("编程"); hobbies.add("游戏"); prototype.setHobbies(hobbies); //拷贝原型对象 ConcretePrototype cloneType = prototype.clone(); cloneType.getHobbies().add("技术控"); System.out.println("原型对象:" + prototype); System.out.println("克隆对象:" + cloneType); System.out.println(prototype == cloneType); System.out.println("原型对象的爱好:" + prototype.getHobbies()); System.out.println("克隆对象的爱好:" + cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); } }
运行结果:
发现,我们给克隆后的对象新增了一个爱好,原型对象也发送了变化,这显然不符合我们的预期。因为我们希望克隆出来的对象应该和原型对象是两个独立的对象,不应该再有联系了,从测试结果来看,应该是 hobbies共用了一个内存地址,意味着复制的不是值,而是引用地址。这样的话,如果修改任意一个对象中属性的值,原型和克隆后的对象值都会变,这就是我们经常说的浅克隆。只是完整复制了值类型数据,没有复制引用对象,换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?我们使用深克隆来改造。
在上面的代码中,我们继续改造,添加一个 deepClone()方法:
@Data public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; private List<String> hobbies; @Override public ConcretePrototype clone() { try { 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 + '}'; } }
测试代码:
public static void main(String[] args) { //创建原型对象 ConcretePrototype prototype = new ConcretePrototype(); prototype.setAge(18); prototype.setName("Jack"); List<String> hobbies = new ArrayList<String>(); hobbies.add("编程"); hobbies.add("游戏"); prototype.setHobbies(hobbies); //拷贝原型对象 ConcretePrototype cloneType = prototype.deepClone(); cloneType.getHobbies().add("技术控"); System.out.println("原型对象:" + prototype); System.out.println("克隆对象:" + cloneType); System.out.println(prototype == cloneType); System.out.println("原型对象的爱好:" + prototype.getHobbies()); System.out.println("克隆对象的爱好:" + cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); }
运行结果:
通过运行结果,发现我们得到了想要的结果。再来看看JDK 中 ArrayList里面的 clone()方法,源码如下:
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ 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); } }
再看 Arrays.copyOf()方法的实现:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
发现是通过浅克隆的方式,只不过将每个元素进行了复制,我们使用 ArrayList.clone()进行改造,属于硬编码,代码如下:
@Data public class ConcretePrototype implements Cloneable, Serializable { private int age; private String name; private List<String> hobbies; @Override public ConcretePrototype clone() { try { return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } /** * 使用 ArrayList 来完成深度克隆 * * @return */ public ConcretePrototype deepCloneHobbies() { try { ConcretePrototype result = (ConcretePrototype) super.clone(); result.hobbies = (List) ((ArrayList) result.hobbies).clone(); return result; } 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 + '}'; } }
运行结果:
运行结果,依然如我们所预料的那样。
如果我们克隆的目标对象是单例对象,那意味着深克隆就会破坏单例(可以参考我的上一篇文章来看如何破坏单例及防止破坏单例)。实际上,防止克隆破坏单例的解决思路非常简单,禁止深克隆即可。要么我们的单例类不实现Cloneable 接口;要么我们重写 clone 方法,直接返回单例的实例化对象;
@Override
public Object clone() {
return INSTANCE;
}
优点:
缺点:
好了,原型模式就写这么多,在实际应用中也会经常见到,比如:BeanUtils.copy…等,慢慢发掘吧!
上一篇:单例模式 https://blog.csdn.net/qq_20315217/article/details/114130214
下一篇:建造者模式 https://blog.csdn.net/qq_20315217/article/details/114282763
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。