前言
设计模式一直都是我们程序语言中较为重要的代码优化套路
为了减少一些冗余代码,提高代码可复用性、可维护性、可读性、稳健性以及安全性。
设计是一种语法规范,是一种为了解决某种特点场景下的某个问题,而提出来的一系列方案。
设计模式一共分为23种,其中分为三种大类型;而我们今天所有讲的是创造型中的原型模式
介绍
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它提供了一种创建对象的最佳方式。
想要使用这种模式需要实现一个接口 Cloneable接口,并重写clone方法,此方法作用是创建一个
调用此方法的对象的副本,就相当于复制一份,但是拷贝的是原型对象的二进制,拷贝完之后的副本对象和原型对象的地址值是不一致的!!!
代码实现浅克隆
定义一个奶牛类,并实现Cloneable
public class Milk implements Cloneable {
private String name;
private String color;
private int milliliter;
private String shapesOfPack;
private Cow cow;
public Milk() {
}
public Milk(String name,String color, int milliliter, String shapesOfPack, Cow cow) {
this.name = name;
this.color = color;
this.milliliter = milliliter;
this.shapesOfPack = shapesOfPack;
this.cow = cow;
}
....包含getset方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
重写了父类中的方法,此方法调用了一个本地方法,也就是c++的方法
定义一个奶牛类
public class Cow {
private String name;
public Cow(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cow{" +
"name='" + name + '\'' +
'}';
}
}
测试代码
//创建纯正特仑苏
Milk milk = new Milk();
//创建特仑苏的奶牛
Cow cow = new Cow("普通奶牛");
milk.setName("特仑苏");
milk.setColor("White");
milk.setMilliliter(250);
milk.setShapesOfPack("正方形");
milk.setCow(cow);
/**
* 经过制作之下,特仑苏销量一飞冲天,此时有不法分子,仿造特仑苏,也做了一款牛奶
*/
Milk cloneMilk = (Milk) milk.clone();//浅克隆
//特仑苏总部发现之后,买来比较
System.out.println(milk.getClass());
System.out.println(cloneMilk.getClass());
System.out.println(milk);
System.out.println(cloneMilk);
/**
* 特仑苏总部发现原料一模一样,于是他们就更换了奶牛
*/
milk.getCow().setName("蒙牛");
System.out.println(milk);
System.out.println(cloneMilk);
System.out.println(cloneMilk.getCow()==milk.getCow());
运行结果,不难发现,我修改了原型对象属性中的cow名字,但是副本对象也会跟着修改
浅克隆的缺点
* 浅克隆缺点暴露,
* 浅克隆只拷贝了基本类型和String类型的数据值,但是对于引用类型,浅克隆直接复制了地址值
* 这就导致原型对象和副本对象的属性都适用了同一片内存空间,那么当有一方修改了值之后,另外一方也会自动修改
解决思路: 我们可以再clone方法中将cow对象重新克隆,这样他们就不会在共享同一片地址
深克隆代码实现
直接将原型对象中依赖的cow对象克隆一份(克隆之后的对象地址不一致)
protected Object clone() throws CloneNotSupportedException {
/** ----------->深度克隆场景
* 特仑苏总部发现每当他们更换原材料的时候,盗版总会跟随更改,
* 那么这个时候他们决定买断蒙牛,只为特仑苏提供原材料,这个时候盗版集团没有办法只能用蒙牛的盗版母牛来做原材料
*
* 实质就是将引用变量重新克隆了一个,使其地址值不一致
*/
Milk cloneMilk = (Milk)super.clone();
Cow clone1 = (Cow)cloneMilk.getCow().clone();//盗版奶牛
cloneMilk.setCow(clone1);
输出结构
那么这个就是我们的深克隆,但是我们需要思考一个问题,这样的深克隆代码有缺点吗?
假如:原型对象中的属性深度(属性是引用类型,且内部又嵌套了对象)较高得情况下,我们需要实现
n多类的方法,显然这是不合理的,所以呢,我们可以使用序列化与反序列化来实现深克隆
Milk milk=null;
try {
OutputStream fos = new FileOutputStream("F:\\a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(this);
InputStream fis = new FileInputStream("F:\\a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
milk = (Milk) ois.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
这样实现的好处是无论你原型对象深度多高,那么在反序列化回来的时候,对象都是不一致的,
但是这样还是有缺点的,我们在类中标明了盘符,这就表示与我们window系统有强耦合关系
如果我需要在linux下实现克隆怎么办??
所以最好的办法是将对象存储到jvm内存中,那么不管你是什么系统,也可以进行
ByteArrayOutputStream流是一个二进制流,可以将对象存取到内存中
ByteArrayIntpuStream 可以将对象从内存中读取出来
ByteArrayOutputStream fos = null;
Milk milk=null;
try {
fos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(this);//将内存中的对象存储
byte[] bytes = fos.toByteArray();
InputStream fis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(fis);
milk = (Milk) ois.readObject();//将内存中的对象取出来
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
那么到现在,我们一整个深克隆的代码优化已经全部完成了
深克隆与浅克隆的区别
浅克隆:在克隆原型对象的时候会将基本类型和String类型数据的值进行拷贝,但是对于引用类型数据
浅克隆是直接拷贝对象的内存地址,这就导致原型对象和副本对象的属性使用了同一片内存空间
深克隆:在克隆原型对象的时候会将基本类型和String类型数据的值进行拷贝,且对于引用类型数据,
也会重 新拷贝一份数据,而不是直接使用原本的地址值,这样原型对象和副本对象的属性就不会使用同一片空间
注意:序列化需要实现Serializable
克隆需要实现Cloneable
场景描述:
(1)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
(2)通过new一个对象需要非常繁琐的数据准备或访问权限,可以使用原型模式。
(3)一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝
还有一点:克隆会破坏单例哦,这是除了反射和反序列化之后出现的第三种破坏单例的模式