一、概念
原型模式(Prototype Pattern):利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。
使用场景:如果对象的创建成本比较大,而且同一个类的不同对象之间差别不大(大部分字段都相同),这种情况下可以考虑原型模式。
二、实现
原型模式有两种实现方法,深拷贝和浅拷贝。
- 浅拷贝:只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象。
- 深拷贝:得到的是一份完完全全独立新的对象。
当一个类中只有基本数据类型时,浅拷贝与深拷贝是同样的。
当一个类中含有引用数据类型是,浅拷贝只是拷贝一份引用,修改浅拷贝的值,原来的也会跟着变化。
举个例子:
肯德基套餐A对象,包含一个可乐对象,一个汉堡包对象。
浅拷贝:就是复制了一份肯德基套餐A,但是里面包含的可乐和汉堡还是原来的那一份,如果咬一口汉堡,那么原来的那个就缺一块。
深拷贝:也是复制了一份肯德基套餐A,但是里面的可乐和汉堡是新的对象,如果咬一口汉堡,那么原来的那个还是完好无损的。
- 浅拷贝实现:
代码:注意如果想实现克隆功能,要克隆的类要实现Cloneable
接口。
1、肯德基套餐A
public class KFCSetMenuA implements Cloneable {
private float price;
private Cola cola;
private Hamburger hamburger;
....省略set和get方法
@Override
protected Object clone() {
KFCSetMenuA kfcSetMenuA = null;
try {
kfcSetMenuA = (KFCSetMenuA) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println(e.toString());
}
return kfcSetMenuA;
}
}
2、其他对象
public class Cola implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Hamburger implements Cloneable {
}
3、实现浅拷贝
public static void main(String[] args) throws CloneNotSupportedException {
KFCSetMenuA kfcSetMenuA = new KFCSetMenuA();
kfcSetMenuA.setPrice(15.5f);
Cola cola = new Cola();
cola.setName("可口可乐");
kfcSetMenuA.setCola(cola);
KFCSetMenuA cloneKFCSetMenuA = (KFCSetMenuA) kfcSetMenuA.clone();
cloneKFCSetMenuA.setPrice(16.5f);
cloneKFCSetMenuA.getCola().setName("百事可乐");
System.out.println("原对象可乐:" + kfcSetMenuA.getCola().getName());
System.out.println("克隆对象可乐:" + cloneKFCSetMenuA.getCola().getName());
System.out.println("原对象价格:" + kfcSetMenuA.getPrice());
System.out.println("克隆对象价格:" + cloneKFCSetMenuA.getPrice());
}
4、浅拷贝结果
总结:从结果看到,说明克隆后的对象和原始的指向的是同一个cola对象,改名字后都变了,但是基本类型数据是没有改变的。
- 深拷贝实现方式1
1、重新定义Cola中的clone
方法。
public class Cola implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@NonNull
@Override
protected Object clone() throws CloneNotSupportedException {
Cola cola = null;
try {
cola = (Cola) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println(e.toString());
}
return cola;
}
}
2、实现的时候同时把Cola对象clone一遍。
public static void main(String[] args) throws CloneNotSupportedException {
KFCSetMenuA kfcSetMenuA = new KFCSetMenuA();
kfcSetMenuA.setPrice(15.5f);
Cola cola = new Cola();
cola.setName("可口可乐");
kfcSetMenuA.setCola(cola);
KFCSetMenuA cloneKFCSetMenuA = (KFCSetMenuA) kfcSetMenuA.clone();
//克隆Cola对象
Cola cloneCola = (Cola) cola.clone();
cloneKFCSetMenuA.setCola(cloneCola);
cloneKFCSetMenuA.setPrice(16.5f);
cloneKFCSetMenuA.getCola().setName("百事可乐");
System.out.println("原对象可乐:" + kfcSetMenuA.getCola().getName());
System.out.println("克隆对象可乐:" + cloneKFCSetMenuA.getCola().getName());
System.out.println("原对象价格:" + kfcSetMenuA.getPrice());
System.out.println("克隆对象价格:" + cloneKFCSetMenuA.getPrice());
}
3、深拷贝实现方式1结果:
总结:这个方式就是把克隆对象中引用的对象也进行浅克隆,但是如果出现嵌套多层的时候,每个引用对象都得实现克隆,太麻烦。
- 深拷贝实现方式2-序列化
1、在KFCSetMenuA 类中加入如下方法,并且KFCSetMenuA 要实现Serializable接口。
public KFCSetMenuA deepClone() {
//声明流对象
ByteArrayOutputStream bos = null;
ByteArrayInputStream bis = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
//创建序列化流
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//将当前对象以对象流的方式输出
oos.writeObject(this);
//创建反序化流
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
//将流对象反序列化,实现类的深拷贝。
return (KFCSetMenuA) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
//关闭资源
bos.close();
bis.close();
oos.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、实现方式
public static void main(String[] args) throws CloneNotSupportedException {
KFCSetMenuA kfcSetMenuA = new KFCSetMenuA();
kfcSetMenuA.setPrice(15.5f);
Cola cola = new Cola();
cola.setName("可口可乐");
kfcSetMenuA.setCola(cola);
// 序列化深拷贝
KFCSetMenuA cloneKFCSetMenuA = kfcSetMenuA.deepClone();
cloneKFCSetMenuA.setPrice(16.5f);
cloneKFCSetMenuA.getCola().setName("百事可乐");
System.out.println("原对象可乐:" + kfcSetMenuA.getCola().getName());
System.out.println("克隆对象可乐:" + cloneKFCSetMenuA.getCola().getName());
System.out.println("原对象价格:" + kfcSetMenuA.getPrice());
System.out.println("克隆对象价格:" + cloneKFCSetMenuA.getPrice());
}
3、深拷贝实现方式2结果:
参考文章:
极客时间《设计模式》(王争)