原型模式
序言:今天我们来聊一下原型模式,我个人认为原型模式的命名不太好理解,称呼其为克隆模式会更妥当一点。原型模式的目的是通过复制一个现有的对象来生成一个新的对象,而不是通过实例化的方法。
原型模式的基本介绍
-
用已经创建的实例对象作为原型,通过复制对象来创建一个和原型相同的对象或相似的新对象
-
原型模式属于创建型模式,主要通过
Cloneable
接口去完成 对象的复制
在原型模式结构图中,会有这么几个角色
- 抽象原型角色(Prototype):是声明克隆方法的接口,是所有原型类的公共父类
- 具体原型角色(Realizetype):它实现在抽象原型类中所声明的克隆方法,在克隆方法中返回一个克隆对象
- 访问角色(PrototypeTest): 使用具体原型类中的 clone() 方法来复制新的对象
需要注意的点:
在 Java 中 能够克隆的 Java类 务必得 实现 Cloneable
接口,表示这个 类 能够被 “复制”,至于这个 复制的效果 则与我们的实现有关,通常 clone()方法满足以下的条件:
- 对任何的对象x,都有:x.clone()!=x 。换言之,克隆对象与元对象不是一个对象
- 对任何的对象x,都有:x.clone().getClass==x.getClass(),换言之,克隆对象与元对象的类型一样
- 对任何的对象x,如果 equals() 方法编写得当的话, 那么x.clone().equals(x)应该是成立的
在正式开始原型模式之前,我们先了解两个概念 浅克隆和深克隆,浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制
原型模式(浅克隆)
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址 复制 一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向 相同 的内存地址
下面我们以 花园和花 为案例
假设我们有一个花园,我们在花园里种植上好的牡丹花,有一天突然想照着牡丹园再建一个一模一样的园子,只是在花园中改种玫瑰
/**
* 花园
*/
@Data
@AllArgsConstructor
public class Garden implements Cloneable {
//面积
private double area;
private Flower flower;
@Override
protected Garden clone() {
Garden garden = null;
try {
garden = (Garden) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower {
private String name;
private String color;
}
下面来测试一下原型模式(浅克隆)
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹园
Garden peonyGarden = new Garden(1000, peony);
System.out.println("初始的牡丹园:" + peonyGarden);
// 牡丹园的复制建造 但是花改成玫瑰 改名玫瑰园
Garden roseGarden = peonyGarden.clone();
//浅复制只会复制引用地址,并没有重新复制一个对象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改为种植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰园:" + roseGarden);
System.out.println("牡丹园:" + peonyGarden);
}
}
我们发现,我们想在新开拓的花园中改种玫瑰后,会影响原花园的花卉的品种,这显然不是我们想要的效果
从这里我们也看出来了,浅克隆 在克隆一个对象的 引用类型的成员变量时 只是复制其地址值,并没有复制该对象
原型模式(深克隆)
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在 Java 中想实现 深克隆,通常有 两种方式
- 对于克隆对象的 引用类型,逐层克隆
- 使用序列化方式
逐层克隆
逐层克隆意味着,如果我们要拷贝一个对象,该对象中 若有多个引用类型的成员变量,它们都要实现克隆方法,若嵌套多层引用类型的成员变量,则逐层 实现 Cloneable
接口
**
* 花园
*/
@Data
@AllArgsConstructor
public class Garden implements Cloneable {
//面积
private double area;
private Flower flower;
@Override
protected Garden clone() {
Garden garden = null;
try {
garden = (Garden) super.clone();
garden.flower = garden.flower.clone();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower implements Cloneable {
private String name;
private String color;
@Override
protected Flower clone() {
Flower flower = null;
try {
flower = (Flower) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return flower;
}
}
下面我们测试一下,使用逐层实现 Cloneable
接口 而完成的深克隆
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹园
Garden peonyGarden = new Garden(1000, peony);
// 牡丹园的复制建造 但是花改成玫瑰 改名玫瑰园
Garden roseGarden = peonyGarden.clone();
// 深克隆 面对引用类型的成员变量 也重新复制了一个对象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改为种植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰园:" + roseGarden);
System.out.println("牡丹园:" + peonyGarden);
}
}
序列化
深克隆模式,采取序列化这种方式可能更简单一些,所以的引用类型 成员变量,都实现序列化接口,原型对象 自实现 deepClone
方法即可
/**
* 花园
*/
@Data
@AllArgsConstructor
public class Garden implements Serializable {
private static final long serialVersionUID = 2231850409918603998L;
//面积
private double area;
private Flower flower;
//深拷贝-方式2 使用序列化方式
public Garden deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Garden garden = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
garden = (Garden) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower implements Serializable {
private static final long serialVersionUID = 126839939664064143L;
private String name;
private String color;
@Override
protected Flower clone() {
Flower flower = null;
try {
flower = (Flower) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return flower;
}
}
下面我们进行序列化深克隆的测试
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹园
Garden peonyGarden = new Garden(1000, peony);
// 牡丹园的复制建造 但是花改成玫瑰 改名玫瑰园
Garden roseGarden = peonyGarden.deepClone();
// 深克隆 面对引用类型的成员变量 也重新复制了一个对象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改为种植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰园:" + roseGarden);
System.out.println("牡丹园:" + peonyGarden);
}
}
从上述我们可以看出,深克隆不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。