一. 什么是原型设计模式?
1.1 原型设计模式的概念
原型设计模式的思想类似于我们常用的复制粘贴功能. 把一个地方的文件复制到另外一个地方, 复制完以后, 两个文件的内容是一模一样的. 原型设计模式的精髓也在于此. 原型模式用于创建重复的对象,首先创建一个实例对象, 然后通过拷贝实例对象创建一个新的对象。这种模式类似于创建型模式。
使用原型模式创建对象非常高效,无须知道对象创建的细节.多用于创建复杂的或者构造耗时的实例,因为在这种情况下,复制一个已经存在的实例会更高效。
1.2 为什么要使用原型设计模式?
- 通常, 类初始化的过程需要消耗很多的资源,这个资源包括数据、空间、时间资源等,通过原型拷贝降低这样的消耗
- 通过new 去创建一个对象,需要非常繁琐的步骤,如:数据准备和检查访问权限等。使用原型模式可以简化这些操作。
- 当一个对象需要被其他对象访问或者操作时, 如果各个调用者都修改数据的可能性,那么这时可以考虑原型设计模式拷贝多个对象以供调用者使用,即保护性复制。
原型设计模式通常使用在new一个资源很耗时的情况,相比new,使用原型模式的效率明显提高。
二. 原型设计模式实现步骤
2.1 原型设计模式的结构
下面来看原型模式的UML图:
从上图中可以看出原型设计模式的结构构成:
- 抽象原型类: 定义了具体原型对象鼻血实现的接口. 这里是Cloneable接口
- 具体原型类: 实现了抽象原型类, 并重写了clone方法.这个类的对象就是可被复制的对象.
- 访问类: 实现使用具体原型类克隆出新类的类
2.2 原型模式实现的步骤
原型模式主要用于对象的复制, 他的核心是Propototype原型类, 下面来看看在java中, 实现原型模式的步骤:
第一步: 原型类Prototype实现Cloneable接口,
第二步: 重写Object的clone()方法.
第三步: 在目标类也就是PrototypeTest类型调用Prototype类的clone方法, 实现对象的复制.
Cloneable接口: 在Java语言中有一个自带的Cloneable接口, 这个接口的作用只有一个, 就是在程序运行的时候通知虚拟机可以安全的在实现了此接口的类上使用clone()方法. 在java虚拟机中, 只有实现了这个接口的类才能被拷贝, 否则会抛出异常CloneNotSupportedException.
public interface Cloneable { }
重写Object的clone()方法: 在java中所有的类都有一个父类Object , Object里面定义了一个clone()方法, 但是这个clone()方法是protected类型的, 在其他地方不能随便使用, 所以, 我们需要重写clone方法, 并将其作用域设置为public.
public class Object { protected native Object clone() throws CloneNotSupportedException; }
原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
三. 原型设计模式的案例
我们就以上面的UML为例, 来感受一下原型设计模式拷贝出的对象
- Prototype原型类, 实现了Cloneable接口, 并重写了Object的clone()方法
public class Prototype implements Cloneable{ private String name; private int age; private String sex; public Prototype(String name, int age, String sex) { this.name = name; this.age = age; this. sex = sex; } /** * 重写object的clone()方法, 并将其作用域设置为public * @return * @throws CloneNotSupportedException */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Prototype{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
- PrototypeTest类, 通过调用Prototype的clone()方法来克隆已经创建的Prototype对象.
public class PrototypeTest { public static void main(String[] args) throws CloneNotSupportedException { Prototype prototype = new Prototype("张三", 8, "男"); Prototype cloneObject = (Prototype)prototype.clone(); System.out.println(cloneObject); } }
运行结果
Prototype{name='张三', age=8, sex='男'}
四. 原型设计模式实现的类型.
原型设计模式实现的类型有两种: 浅拷贝和深拷贝
4.1 浅拷贝
浅拷贝指的是在创建一个对象的时候, 新对象的属性和原来对象的属性完全相同, 对于非基本类型属性, 扔指向原有属性所指向的对象的内存地址。
还是用上面的demo来说明:
public class Prototype implements Cloneable{ private String name; private int age; private String sex; private ArrayList<String> hobbies; public Prototype(String name, int age, String sex, ArrayList<String> hobbies) { this.name = name; this.age = age; this. sex = sex; this.hobbies = hobbies; } /** * 重写object的clone()方法, 并将其作用域设置为public * @return * @throws CloneNotSupportedException */ @Override public Prototype clone() throws CloneNotSupportedException { Prototype clone = (Prototype)super.clone(); return clone; } } public class PrototypeTest { public static void main(String[] args) throws CloneNotSupportedException { ArrayList hobbies = new ArrayList(); hobbies.add("篮球"); hobbies.add("排期"); Prototype prototype = new Prototype("张三", 8, "男", hobbies); Prototype cloneObject = (Prototype)prototype.clone(); System.out.println("比较克隆前后的对象:"+(prototype == cloneObject)); System.out.println("比较克隆前后的List<String>属性:" + (prototype.getHobbies() == cloneObject.getHobbies())); } }
运行结果:
比较克隆前后的对象:false
比较克隆前后的List属性:true
我们在比较hobbies的时候, 使用的是, 的含义是地址和值都一样才返回true。
第一个返回结果是false。说明克隆后重新创建了一个对象。
第二个结果返回的是ture,说明克隆后引用类型的对象指向了原来对象的地址。
这是一种浅拷贝, 默认的拷贝方式是浅拷贝。
4.2 深拷贝
深拷贝是指在创建一个对象的时候, 属性中引用的其他对象也会被克隆,不再指向原有的地址。
我们还是使用上面的案例, 将上面的浅拷贝变成深拷贝
public class Prototype implements Cloneable{ private String name; private int age; private String sex; private ArrayList<String> hobbies; public Prototype(String name, int age, String sex, ArrayList<String> hobbies) { this.name = name; this.age = age; this. sex = sex; this.hobbies = hobbies; } /** * 重写object的clone()方法, 并将其作用域设置为public * @return * @throws CloneNotSupportedException */ @Override public Prototype clone() throws CloneNotSupportedException { Prototype clone = (Prototype)super.clone(); System.out.println("浅拷贝:" + (clone.hobbies == this.hobbies)); clone.hobbies = (ArrayList<String>) (this.hobbies).clone(); System.out.println("深拷贝:" + (clone.hobbies == this.hobbies)); return clone; } @Override public String toString() { return "Prototype{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; } } public class PrototypeTest { public static void main(String[] args) throws CloneNotSupportedException { ArrayList hobbies = new ArrayList(); hobbies.add("篮球"); hobbies.add("排期"); Prototype prototype = new Prototype("张三", 8, "男", hobbies); Prototype cloneObject = (Prototype)prototype.clone(); System.out.println("比较克隆前后的对象:"+(prototype == cloneObject)); System.out.println("比较克隆前后的List<String>属性:" + (prototype.getHobbies() == cloneObject.getHobbies())); } }
运行结果:
浅拷贝:true
深拷贝:false
比较克隆前后的对象:false
比较克隆前后的List属性:false
这是一种深拷贝, 实现方式是
public Prototype clone() throws CloneNotSupportedException { Prototype clone = (Prototype)super.clone(); System.out.println("浅拷贝:" + (clone.hobbies == this.hobbies)); clone.hobbies = (ArrayList<String>) (this.hobbies).clone(); System.out.println("深拷贝:" + (clone.hobbies == this.hobbies)); return clone; }
对ArrayList使用了clone。这样就保障对象重新创建。
原型模式是比较简单的一个模式,核心就是对原始对象进行拷贝,为了减少错误,建议每次都进行深拷贝。
五. 原型设计模式的优缺点
5.1 优点
在内存中二进制流进行拷贝,要比直接new 一个对象性能要好。
- 如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率
- 可以使用深拷贝保持原始对象的状态
- 原型模式提供了简化的创建结构
5.2 缺点
直接在内存中进行拷贝,是不会执行构造函数的,减少了约束。优点是减少了约束,缺点也是减少了约束。
- 在实现深拷贝的时候可能需要比较复杂的代码
- 需要为每一个类配备一个clone 方法,而且这个clone 方法需要对类的整体功能进行考虑,这对全新的类来说并不困难,但对已有的类进行改造时,并不是一件容易的事,必须修改其源代码,违背了“开闭原则”。
六. 原型模式的注意事项
使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。
不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如
public class Prototype implements Cloneable{ private String name; private int age; private String sex; private ArrayList<String> hobbies; /** * 重写object的clone()方法, 并将其作用域设置为public * @return * @throws CloneNotSupportedException */ @Override public Prototype clone() throws CloneNotSupportedException { Prototype clone = (Prototype)super.clone(); clone.hobbies = (ArrayList<String>) (this.hobbies).clone(); return clone; } }
由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。
七. 原型设计模式的应用场景
7.1 原型模式适用的场景:
- 通常, 一个类很复杂的时候, 包含了很多种数据和结构,数据结构层次又比较深时,适用于原型模式。
- 当复杂的对象需要独立于系统运行, 而不能破坏本系统中的结构时。
7.2 实例场景:
- 一个楼盘有名称,地址和施工队三个成员变量。施工队有名称,人数和包工头。包工头有名称和年龄。现在要建设一个隔壁的楼盘,还是由这个施工队进行建设的,只是地址不同。如果重新创建,过程较为复杂,费时费力,采取原型模式可以快速构建一个新的楼盘。
- 系统中已经有一架飞机,飞机有名称和型号和厂商。厂商有名称,地址和负责人。负责人有姓名和年龄。现在要一家相同的飞机由不同的负责人进行指导生产的,如何快速创建这样的对象。
八. 原型设计模式运用了那几大原则
我们来盘点一下设计模式的六大原则, 看看上面的原型模式案例应用了设计模式六大原则的哪几类模型
- 单一职责原则:类很简单, 符合
- 里式替换原则:重写了父类的clone方法,不符合
- 接口隔离原则:最小接口原则,符合
- 依赖倒置原则:不依赖于具体,依赖于抽象,接口隔离原则实现了cloneable接口,符合
- 迪米特法则:类之间有最好了联系。借口隔离原则很简单,也符合
- 开放封闭原则:对扩展开放,对修改关闭。符合