一、引出原型模式
如果我们有一个类(sheep),它里面有两个属性,名称(name),年龄(age)。现在我们有一个它的实例(s1),我们需要按照这个实例的属性再去创建两个对象。
1、Sheep
@Data
public class Sheep {
private String name;
private Integer age;
Sheep(String name,Integer age){
this.name = name;
this.age = age;
}
}
2、main
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
Sheep s2 = new Sheep(s1.getName(),s1.getAge());
Sheep s3 = new Sheep(s1.getName(),s1.getAge());
}
}
总结:
- 这样写看起来还好不麻烦也没有问题
- 但是如果这类里面有100个属性呢?(我们写的代码会很多)
- 或者我们现在对这个类再添加几个属性呢?(我们需要修改的地方很多)
二、原型模式
原型模式 : 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
也就是我们使用clone方法进行对象的创建,我们对上面的 sheep 类新增clone方法。
1、改造后的sheep类
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class Sheep implements Cloneable{
private String name;
private Integer age;
Sheep(String name,Integer age){
this.name = name;
this.age = age;
}
@Override
protected Sheep clone() {
Object obj = null;
try {
obj = super.clone();
}catch (Exception e){
e.printStackTrace();
}
return (Sheep)obj;
}
}
2、main 测试
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // false
}
}
总结
- 使用clone方法去创建对象,就解决上面的问题了。
- 但其我们的clone方法是一个浅拷贝,如果类里面有引入属性,那么创建出来的对象的引用都是指向一个的。
我们对 浅拷贝 进行一个测试
1、在上面的 sheep 类里面新增一个 属性
private Sheep friend;
2、测试
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
s1.setFriend(new Sheep("小道仙",2));
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
s2.getFriend().setName("王哈哈");
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
}
// 打印结果如下
小道仙 小道仙 小道仙
王哈哈 王哈哈 王哈哈
}
三、浅拷贝、深拷贝
浅拷贝 :在拷贝对象的时候对引用类型,没有从新创建一个对象,而是指向之前对象的引用。
深拷贝 :解决浅拷贝存在的问题,我们去为引用类型创建一个新的对象。
我们使用深拷贝去解决浅拷贝的问题
方式一:使用多次clone
@Override
protected Sheep clone() {
Sheep obj = null;
try {
obj = (Sheep)super.clone();
if (this.getFriend() != null){
Sheep clone = this.getFriend().clone();
obj.setFriend(clone);
}
}catch (Exception e){
e.printStackTrace();
}
return obj;
}
- 从上面的代码我们可以看到所谓多次克隆,也就是对引用类型再进行一次克隆。
- 优点:它写起来和理解都很简单。
- 缺点:如果引用类型过多那么写起来很麻烦,后续如果 新增/删除 引用类型,我们还需要修改 clone 方法。
方式二:使用序列化和反序列化 (注:需要实现序列化接口 implements Serializable)
@Override
protected Sheep clone() {
Sheep obj = null;
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
obj = (Sheep)ois.readObject();
return obj;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
- 使用序列化优势很明显,后续 新增/删除 引用类型,我们不需要修改 clone 方法,只不过写起来稍微复杂一点。但是推荐使用这个。
两种方法的测试代码如下,结果也都一样
public class Main {
public static void main(String[] args) {
Sheep s1 = new Sheep("多利",1);
s1.setFriend(new Sheep("小道仙",2));
Sheep s2 = s1.clone();
Sheep s3 = s1.clone();
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
s2.getFriend().setName("王哈哈");
System.out.println(s1.getFriend().getName() + " " + s2.getFriend().getName() + " " + s3.getFriend().getName());
// 打印结果
小道仙 小道仙 小道仙
小道仙 王哈哈 小道仙
}
}