2、6 原型模式
定义
1)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2)这种不通过 new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
3)原型模式先产生一个包含大量共用信息的类,然后拷贝出副本,修正细节信息,建立了一个完整的个性对象。
4)一个对象的产生可以不由零起步,直接从一个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。
5)原型模式其实就是从一个对象再创建另外一个可定制的对象,而不需要知道任何创建的细节。
6)一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高(相对每次 new 来说)。
7)不用初始化对象,而是动态地获得对象运行时的状态。
8)当创建给定类的实例的过程很昂贵或很复杂时,就使用原型模式。
9)原型模式允许你通过复制现有的实例来创建新的实例(在 Java 中,这通常意味着使用 clone()方法,或者反序列化)。这个模式的重点在于,客户的代码在不知道要实例化何种特定类的情况下,可以制造出新的实例。
通用类图
原型模式的核心是一个 clone 方法,通过该方法进行对象的拷贝,Java 提供了一个 Cloneable 接口来标识这个对象是可拷贝的。这个接口只是一个标记作用,这个 JVM 中具有这个标记的对象才有可能被拷贝。覆盖/重写 clone()方法才能从”有可能被拷贝”转换为”可以被拷贝”。
通用源码
public class Client1 {
}
class PrototypeClass implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return prototypeClass;
}
}
优点缺点
1)性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2)逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要在实际应用时考虑。
3)向客户隐藏制造新实例的复杂性。
4)提供让客户能够产生未知类型对象的选项。
5)在某些环境下,复制对象比创建新对象更有效。
使用原型模式的缺点:对象的复制有时相当复杂。
适用场景
1)资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
2)性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
3)一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且多个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
4)线程不安全也可以通过对象的复制功能来解决这个问题。
5)在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时,可以考虑原型。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
注意事项
构造函数不会被执行
案例
public class Client2 {
public static void main(String[] args) {
//产生一个对象
System.out.println("1111111111111111");
Thing thing = new Thing();
System.out.println("2222222222222222");
//拷贝一个对象
System.out.println("3333333333333333");
Thing cloneThing = thing.clone();
System.out.println("4444444444444444");
}
}
class Thing implements Cloneable{
public Thing() {
System.out.println("构造函数被执行");
}
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return thing;
}
}
1111111111111111
构造函数被执行
2222222222222222
3333333333333333
4444444444444444
描述
对象拷贝时构造函数确实没有被执行,因为 Object 类的 clone 方法的原理是从内存中(具体来说是从堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,所以构造函数没有被执行也是非常正常的了。
浅拷贝
案例
import java.util.ArrayList;
import java.util.List;
public class Client3 {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
thing.setValue("zhangsan");
thing.setNum1(11);
thing.setNum2(12);
CloneModel cloneModel1 = new CloneModel();
cloneModel1.setStrtest("strtestval1");
thing.setCloneModel(cloneModel1);
thing.setStr1("str11val");
//拷贝一个对象
Thing cloneThing1 = thing.clone();
cloneThing1.setValue("lisi.wangwu.");
cloneThing1.setNum1(21);
cloneThing1.setNum2(22);
CloneModel cloneModel2 = new CloneModel();
cloneModel2.setStrtest("strtestval2");
cloneThing1.setCloneModel(cloneModel2);
cloneThing1.setStr1("str12val");
System.out.println("----------clone-before-----------");
System.out.println(thing.getValue());
System.out.println(thing.getNum1());
System.out.println(thing.getNum2());
System.out.println(thing.getStr1());
System.out.println(thing.getCloneModel().getStrtest());
System.out.println("----------clone-after-----------");
System.out.println(cloneThing1.getValue());
System.out.println(cloneThing1.getNum1());
System.out.println(cloneThing1.getNum2());
System.out.println(cloneThing1.getStr1());
System.out.println(cloneThing1.getCloneModel().getStrtest());
}
}
class Thing implements Cloneable{
private List<String> arrayList = new ArrayList<>();
private CloneModel cloneModel = new CloneModel();
private String str1 = "str1val";
private int num1 = 1;
private Integer num2 = 1;
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return thing;
}
public void setValue(String value){
this.arrayList.add(value);
}
public List<String> getValue(){
return this.arrayList;
}
public void setStr1(String str){
this.str1 = str;
}
public String getStr1(){
return this.str1;
}
public void setNum1(int num){
this.num1 = num;
}
public int getNum1(){
return this.num1;
}
public void setNum2(int num){
this.num2 = num;
}
public int getNum2(){
return this.num2;
}
public CloneModel getCloneModel() {
return cloneModel;
}
public void setCloneModel(CloneModel cloneModel) {
this.cloneModel = cloneModel;
}
}
class CloneModel{
private String strtest;
public String getStrtest() {
return strtest;
}
public void setStrtest(String strtest) {
this.strtest = strtest;
}
}
----------clone-before-----------
[zhangsan, lisi.wangwu.]
11
12
str11val
strtestval1
----------clone-after-----------
[zhangsan, lisi.wangwu.]
21
22
str12val
strtestval2
描述
可以发现案例中输出了 lisi.wangwu。因为 Object 类提供的方法 clone 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。(一种非常不安全的方式)
注意:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:
一是类的成员变量,而不是方法内变量;
二是必须是一个可变的引用对象,而不是一个原始类型(比如 int、long、char 等)或不可变对象(String 是没有 clone 方法的,处理机制也比较特殊,通过字符串池 stringpool 在需要的时候才在内存中创建新的字符串。)
深拷贝
案例
import java.util.ArrayList;
import java.util.List;
public class Client4 {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
thing.setValue("zhangsan");
//拷贝一个对象
Thing cloneThing = thing.clone();
cloneThing.setValue("lisi.wangwu.");
System.out.println(thing.getValue());
}
}
class Thing implements Cloneable{
//这里要用ArrayList而不是List(要指明具体的类型)
//这里不要加final(要使用clone方法,类的成员变量上不要增加final关键字,用了final怎能重新赋值?)
private ArrayList<String> arrayList = new ArrayList<>();
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing)super.clone();
//这里一定要加上thing
//注意不会被拷贝的条件:可变的引用对象不会被拷贝。
//因为个人理解上为这个实体的clone和集合的clone本质并不是一体的,虽然在同一个方法体中且在方法内来处理。
thing.arrayList = (ArrayList<String>) this.arrayList.clone();
}catch (Exception e){
e.printStackTrace();
}
return thing;
}
public void setValue(String value){
this.arrayList.add(value);
}
public List<String> getValue(){
return this.arrayList;
}
}
[zhangsan]
描述
该方法实现了完全的拷贝,两个对象之间没有任何的瓜葛。深拷贝还有一种实现方式就是通过写二进制流来操作对象,然后实现深拷贝。
标题:设计模式(六)创建型模式(对象创建型模式)原型模式-PROTOTYPE
作者:yazong
地址:https://blog.llyweb.com/articles/2020/06/04/1591281200167.html