什么是原型模式
原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象
在应用程序中,有些对象比较复杂,其创建过程过于复杂,而且我们又需要频繁的利用该对象,如果这个时候我们按照常规思维new该对象,那么务必会造成资源浪费,这个时候我们就希望可以利用一个已有的对象来不断对他进行复制就好了,这就是编程中的“克隆”。原型模式直接操作底层二进制流,在创建复杂对象是效率提升明显。
分类:
浅克隆与深克隆:
浅克隆:当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。
深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将被复制
浅克隆
原型类Prototype
//原型类
public class Prototype implements Cloneable {
public Prototype close(){
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
子类实现类
public class ConcretePrototype extends Prototype {
private String name;
private final int age;
private ArrayList data = new ArrayList<>();
public ConcretePrototype() {
this.name = "Tom";
this.age = 12;
this.data.add("take your time");
}
@Override
public String toString() {
return "ConcretePrototype{" +
"name='" + name + '\'' +
", age=" + age +
", data=" + data +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public ArrayList getData() {
return data;
}
public void setData(ArrayList data) {
this.data = data;
}
}
测试类
public class ClientText {
public static void main(String[] args) {
ConcretePrototype concretePrototype = new ConcretePrototype();
ConcretePrototype concretePrototype1 = (ConcretePrototype)concretePrototype.close();//复制出的新对象
System.out.println("=====数据修改前=====");
System.out.println(concretePrototype.toString());
System.out.println(concretePrototype1.toString());
concretePrototype1.setName("my");
concretePrototype1.getData().add("new");
System.out.println("=====数据修改后=====");
System.out.println(concretePrototype.toString());
System.out.println(concretePrototype1.toString());
}
}
运行结果:
=====数据修改前=====
ConcretePrototype{name='Tom', age=12, data=[take your time]}
ConcretePrototype{name='Tom', age=12, data=[take your time]}
=====数据修改后=====
ConcretePrototype{name='Tom', age=12, data=[take your time, new]}
ConcretePrototype{name='my', age=12, data=[take your time, new]}
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显,使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
从上面可以看出当复制出的新对象的list被修改时原对象list也会被改变,下面我们来说下原因:
Object类的clone方法只会拷贝对象中的基本的数据类型(8种基本数据类型byte,char,short,int,long,float,double,boolean),对于数组、容器对象、引用对象等拷贝的是对应的内存地址,拷贝的list是指向的同一个内存地址,这就是浅拷贝。
深克隆
基于上面的问题我们我们对代码进行了修改:
子类重写close方法并增加对ArrayList进行close操作
public class ConcretePrototype extends Prototype {
private String name;
private final int age;
private ArrayList data = new ArrayList<>();
@Override
public Prototype close(){
ConcretePrototype concretePrototype = null;
try {
concretePrototype = (ConcretePrototype) super.clone();
concretePrototype.data = (ArrayList) this.data.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return concretePrototype;
}
public ConcretePrototype() {
this.name = "Tom";
this.age = 12;
this.data.add("take your time");
}
@Override
public String toString() {
return "ConcretePrototype{" +
"name='" + name + '\'' +
", age=" + age +
", data=" + data +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public ArrayList getData() {
return data;
}
public void setData(ArrayList data) {
this.data = data;
}
}
测试类
public class ClientText {
public static void main(String[] args) {
ConcretePrototype concretePrototype = new ConcretePrototype();
ConcretePrototype concretePrototype1 = (ConcretePrototype)concretePrototype.close();//复制出的新对象
System.out.println("=====数据修改前=====");
System.out.println(concretePrototype.toString());
System.out.println(concretePrototype1.toString());
concretePrototype1.setName("my");
concretePrototype1.getData().add("new");
System.out.println("=====数据修改后=====");
System.out.println(concretePrototype.toString());
System.out.println(concretePrototype1.toString());
}
}
输出
=====数据修改前=====
ConcretePrototype{name='Tom', age=12, data=[take your time]}
ConcretePrototype{name='Tom', age=12, data=[take your time]}
=====数据修改后=====
ConcretePrototype{name='Tom', age=12, data=[take your time]}
ConcretePrototype{name='my', age=12, data=[take your time, new]}
关于深拷贝和浅拷贝,会发生深拷贝的是java 的 8种基本数据类型和他们的封装类,至于String这个类型需要注意,它是引用数据类型,所以是浅拷贝。
总结
- 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
- 如果原型模式中的数组、容器对象、引用对象作为成员变量被finali修饰时,无法对它们单独clone,因为final修饰的变量需要初始化,不能重新赋值
- Object.clone()方法是一个native方法,简单地讲,一个native方法就是一个java调用非java代码的接口。而一般native方法的速度都要比你自己所写的程序运行速度快很多,这也是为什么当我们想要对一个对象进行克隆操作时,推荐使用Object.clone()方法而非自己通过java代码去实现这样一个功能(虽然也可以达到想要的结果)。
- 关于clone的访问权限问题
protected方法,只能是子类内部或者同一个包中的其他类可以使用,这里你的自己的类肯定不会和Object在一个包内,因此必须子类才能使用,Object是所有类的父类,因此在你自定义的这个类的内部是可以使用clone这个方法的,但是问题就在这里,如果你想克隆类A,你不能在一个包含类A的类B内,使用A.clone方法,这会编译器报错‘clone()
has protected access in ‘java.lang.Object’’。但是如果在类A内部使用A.clone方法,虽然不会编译器报错‘clone() has protected access in
‘java.lang.Object’’,却会报错Unhandled
exception:java.lang.CloneNotSupportedException,这已经不是刚才的protected访问权限的问题了,这个问题的原因是如果一个类没有实现Cloneable接口,或者一个子类试图重写clone方法都会抛出CloneNotSupportedException这个异常,因此必须对这个类实现Cloneable接口,并且将clone()方法重写为public。