原型模式的基本概念
原型模式就是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
它是一种创建型模式,简单说,原型模式就是从一个对象创建出另一个对象,而不必知道他的创建细节。或者可以把原型模式叫做-克隆。被复制的对象被叫做-原型。
什么时候用原型模式
原型模式的使用场景如下:
- 当类的大量调用并且初始化需要耗费大量的资源。
- 保护原始对象在多个地方调用后的原始性。
- 对象的创建过程复杂或需要复杂的权限操作创建。
原型模式怎么用
原型模式分成浅复制和深复制。
浅复制是指复制了对象,数值类型的会直接复制,但是引用类型的变量复制的是对象的引用,所以浅复制对对象引用类型的改变也导致原对象的引用类型也同时改变,所以这被称作浅复制。
深复制的复制则代表产生一个新的与原有对象无关的复制对象。
关于值复制和引用复制
int a = 1;
int b = a;
这就是一种值的复制。
String a = new String(“123”);
String b = a;
这里就是一种引用类型的复制,b的引用就是a的引用,所以对b的修改也会导致a修改。
关于引用、指向这些有疑问的可以看看我的关于引用、指向这些有疑问的可以看看我的
http://blog.youkuaiyun.com/chijiandi/article/details/78977535
以发简历为例,一份简历的创建我们需要设置我们个人的信息等等情况,但是在对不同的公司进行投递时又需要进行可变的修改,在没学原型模式之前会是大量的new的创建,若一个对象的创建很复杂,资源便大量的被浪费。
浅复制
原型模式的产生基于java的一个Cloneable接口,如果我们只是简单的实现了这个接口并通过super.clone();进行的复制就是一种简单的浅复制,这种复制其实是一种假复制。
新建一个简历类实现了Cloneable的接口并重写clone():
/**
* @author : cjd
* @Description : 简历原型模式
* @create : 2018-02-02 9:29
**/
public class Resume implements Cloneable {
private String name ;
private int age;
private char sex;
private Experience experience;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Resume() {
experience = new Experience();
}
public void setExperienceData(String data) {
experience.setData(data);
}
public void display(){
System.out.println("姓名:" + name+ " 年龄:" +age +" 性别:"+sex+" 经历:"+experience.getData());
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
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;
}
}
String 是一种特殊的引用类型,运用上其实和值类型更像,所以这里我也拿出来进行了比较,为了验证浅复制创建了一个引用类型Experience,在简历类实例化的同时实例化Experience,同时留一个接口用来改Experience的值。
/**
* @author : cjd
* @Description : 经历类
* @create : 2018-02-02 9:52
**/
public class Experience {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
主函数进行这样的调用:
/**
* @author : cjd
* @Description : 主函数
* @create : 2018-02-02 9:35
**/
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Resume cjdOne = new Resume();
cjdOne.setName("cjdOne");
cjdOne.setAge(22);
cjdOne.setSex('男');
cjdOne.setExperienceData("cjdOne和人比帅,独孤求败。");
Resume cjdTwo = (Resume) cjdOne.clone();
cjdTwo.setName("cjdTwo");
cjdTwo.setAge(18);
cjdTwo.setSex('女');
cjdTwo.setExperienceData("cjdTwo和人比帅,独孤求败。");
cjdOne.display();
cjdTwo.display();
}
}
得到的结果是
姓名:cjdOne 年龄:22 性别:男 经历:cjdTwo和人比帅,独孤求败。
姓名:cjdTwo 年龄:18 性别:女 经历:cjdTwo和人比帅,独孤求败。
可以看到,数值类型的包括String这个特殊的引用类型都复制过来并且改掉了,但是引用类型Experience在cjdTwo中改变了,这个改变却导致了cjdOne的改变,这就是浅复制的缺陷,而我们更多情况下需要的是复制一个完全可以更改的不会导致原对象改变的对象,也就是深复制。
深复制
实现深复制我们需要对代码进行这样的处理。
1.让需要深复制的引用类型实现Cloneable接口,并重写clone。
2.在复制的类中复制的同时复制引用类型。
所以修改之后我们的Experience加了一个clone的方法
/**
* @author : cjd
* @Description : 经历类
* @create : 2018-02-02 9:52
**/
public class Experience implements Cloneable {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然后在Resume中修改克隆方法:
@Override
protected Object clone() throws CloneNotSupportedException {
Resume resume = (Resume) super.clone();
resume.experience = (Experience) experience.clone();
return resume;
}
主函数调用结果如下:
姓名:cjdOne 年龄:22 性别:男 经历:cjdOne和人比帅,独孤求败。
姓名:cjdTwo 年龄:18 性别:女 经历:cjdTwo和人比帅,独孤求败。
后记
最后的结论就是,简单的super.clone只是复制值类型,而其中的引用类型是不会进行复制的,如果要实现深复制,就需要在super.clone的同时,将引用类型也clone。
这无法避免的弊端就是,我们在实现必须明确需要进行克隆的类,这也无法避免对原有代码进行修改的情况,显然这违背了开闭原则。
第二点弊端是,如果发生了克隆之中的镶嵌引用,要互相进行克隆,自然代码的复杂性会提示。