运行结果
克隆会破坏单例模式
如果我们克隆的目标对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例解决思路非常简单,禁止深克隆便可。要么我们的单例类不实现Cloneable接口;要么我们重写clone()方法,在clone方法中返回单例对象即可,具体代码如下:
@Override protected object clone() throws CloneNotSupportedException { return instance; }
原型模式在源码中的应用
先来JDK中Cloneable接口:
public interface Cloneable{ }
接口定义还是很简答的,我们找源码其实只需要找到看哪些接口实现了Cloneable即可。来看ArrayList类的实现。
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); } }
我们发现方法中知识将List中的元素循环遍历了一遍。这个时候我们再思考一下,是不是这种形式就是深克隆呢?其实用代码验证一下就知道了,继续修改ConcretePrototype类,增加一个deepCloneHobbies()方法:
public ConcretePrototype deepClone(){ try { ConcretePrototype result = (ConcretePrototype) super.clone(); result.hobbies = (List) ((ArrayList)result.hobbies).clone(); return result; }catch (CloneNotSupportedException e){ e.printStackTrace(); return null; } }
修改客户端代码:
public static void main(String[] args) { ... //拷贝原型对象 ConcretePrototype cloneType = prototype.deepCloneHobbies(); ... }
运行也能得到期望的结构。但是这样的代码,其实是硬编码,如果在对象中声明了各种集合的类型,那每种情况都需要单独处理。因此,申客隆的写法,一般会直接用序列化来操作。
原型模式的优缺点:
优点:
1、性能优良,Java自带的 原型模式 是基于内存二进制流的拷贝,比直接new 一个对象性能上提升了许多。
2、可以使用深克隆方式保存对象的状态,使用原型模式对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历时某一状态),可辅助实现撤销操作。
缺点:
1、需要为每一个类配置一个克隆放啊。
2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
3、在实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此深拷贝、浅拷贝需要运用得当。
二、建造者模式
建造者模式(Builder Pattern)是一个将复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示,属于创建型模式。使用建造者模式对于用户而言只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
建造者模式适用于创建对象需要很多步骤,但是步骤的顺序不一定固定。如果一个对象有非常复杂的内部结构(很多属性),可以将复杂对象的创建和使用进行分离。
使用场景
这个就非常重要了,因为如果你学了个东西,都不知道用来解决什么问题,你说有什么用?理解使用场景的的重要性要远高于你是不是会实现这个模式,因为只要你知道什么问题可以使用builder模式来解决,那你即使不会写,也可以在调查相关资料后完成。 我不想说一些大而正确的术语来把你搞蒙,我们只针对具体的问题,至于延展性的思考,随着你知识的增长,逐渐会明白的。延展阅读
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
解决的问题
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。 例如我们现在有如下一个类计算机类Computer,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer { private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 }
第一:折叠构造函数模式(telescoping constructor pattern ),这个我们经常用,如下代码所示
public class Computer { ... public Computer(String cpu, String ram) { this(cpu, ram, 0); } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "罗技键盘"); } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "三星显示器"); } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } }