文章目录
一 、原型模式的简介
1. 什么是原型模式
原型模式(prototype) 是创建型设计模式的一种,通常原型模式是用来创建属性相同的对象,其将对象的创建过程封装起来,业务调用者无需关心对象的创建细节及原理。
原型模式应该是23种设计模式中最简单的几种,其本身已经被Java语言实现了一部分,因此大多数情况下我们只需要使用即可。
2. 原型模式应用的业务场景
原型模式通常被用在创建多个重复的相同对象时,我们可以理解是在实现对象的自我复制时需要使用原型模式。举个最简单的例子,西游记里的孙悟空通过猴毛变成千百个孙悟空亦或是火影忍者里鸣人通过多重影分身分裂出多个鸣人一样,原型模式就是一个通过最初的实体复制成多个对象的手段。
二、原型模式的实现
1. 原型模式的实现原理
通过Java语言实现原型模式很简单,由于Object对象有一个自带的clone方法,因此我们只需要调用clone方法即可。这里需要注意由于Object对象本身并没有实现cloneable接口,因此我们想要使用clone方法之前需要为我们的原型对象实现cloneable接口。
2. 原型模式的简单实现
这里我们就简单的模拟一个孙悟空变猴子猴孙的实现:
2.1 创建一个原型对象
public class Monkey implements Cloneable {
private String name;
private String age;
@Override
protected Monkey clone() throws CloneNotSupportedException {
// 这里先不修改浅拷贝相关代码
return (Monkey) super.clone();
}
public Monkey(String name, String age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Monkey [name=" + name + ", age=" + age + "]";
}
}
2.2 测试原型对象
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Monkey monkey = new Monkey("孙悟空", "三百四十二岁");
Monkey clone = monkey.clone();
System.out.println(monkey.equals(clone));
System.out.println(clone.toString());
}
}
测试结果:
这里我们看见一个新的对象已经创建成功了,但是其实这里还是存在着一点问题,我们接下来详细的讲一下。
3. 直接调用clone方法存在的问题
如果我们此时在实体中新增一个引用数据类型的属性,通过clone方法复制后这个属性会不会创建一个新的对象呢?这里我们简单的修改一下代码,新增一个职业的集合,在有参构造方法中为他提供一个默认的赋值。
public class Monkey implements Cloneable {
private String name;
private String age;
// 职业
private List vocation;
@Override
protected Monkey clone() throws CloneNotSupportedException {
// 这里先不修改浅拷贝相关代码
return (Monkey) super.clone();
}
public Monkey(String name, String age) {
super();
this.name = name;
this.age = age;
this.vocation = new ArrayList<>();
}
@Override
public String toString() {
return "Monkey [name=" + name + ", age=" + age + ", vocation=" + vocation + "]";
}
public List getVocation() {
return vocation;
}
}
然后让我们来观察下结果
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Monkey monkey = new Monkey("孙悟空", "三百四十二岁");
Monkey clone = monkey.clone();
System.out.println(monkey.equals(clone));
System.out.println(clone.getVocation().equals(monkey.getVocation()));
}
}
这里我们观察到原型对象中的List属性并没有在克隆对象中重新分配一个地址,而是原型对象和克隆对象共用同一片内存空间,这无疑是会引起变量安全性的严重问题,造成这个问题的根本原因是因为Object的clone方法默认的是实现了浅拷贝,而浅拷贝在遇到引用类型的时候就会出现这个问题。
三、 深拷贝与浅拷贝
1. 什么是深拷贝与浅拷贝
浅拷贝是指我们在clone对象的时候只会将对象的引用值复制过来,新的对象与老的对象共用同一片内存地址,这样对新的对象中的值进行操作,老的对象也可以感知。JDK的clone方法默认的就是实现的浅拷贝。
深拷贝与浅拷贝不同,深拷贝会为新的对象创建一个新的内存空间,然后将旧的引用对象中的变量值赋予新的对象。新旧对象之间的内存指向不同,对新的对象的值进行操作,老的对象并不会感知。
2. 如何实现一个深拷贝
深拷贝的实现方式主要有两种方式:
- 双重浅拷贝
- 序列化与反序列化
四 、 深拷贝原型模式的实现
1. 双重浅拷贝
双重浅拷贝的实现很简单,就是通过为原型类中的引用数据类型也创建一个原型副本达到创建新的内存引用的目的。
测试代码:
public class Monkey implements Cloneable {
private static final String Monkey = null;
private String name;
private String age;
// 职业
private ArrayList vocation;
@Override
protected Monkey clone() throws CloneNotSupportedException {
Monkey clone = (Monkey) super.clone();
ArrayList copy = clone.getVocation();
ArrayList shallowCopy = (ArrayList) copy.clone();
ArrayList deepCopy = new ArrayList<>();
deepCopy.add(shallowCopy);
clone.setVocation(deepCopy);
return clone;
}
public void setVocation(ArrayList vocation) {
this.vocation = vocation;
}
public Monkey(String name, String age) {
super();
this.name = name;
this.age = age;
this.vocation = new ArrayList<String>();
this.vocation.add("齐天大圣");
}
@Override
public String toString() {
return "Monkey [name=" + name + ", age=" + age + ", vocation=" + vocation + "]";
}
public ArrayList getVocation() {
return vocation;
}
}
运行结果:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Monkey monkey = new Monkey("孙悟空", "三百四十二岁");
Monkey clone = monkey.clone();
System.out.println(monkey.equals(clone));
System.out.println(clone.getVocation().equals(monkey.getVocation()));
}
}
这里我们发现,双重浅拷贝方式虽然可以实现深拷贝,但是如果引用类型过多,我们需要单独的将每一种引用类型进行处理,代码繁琐,并不推荐日常使用。
2. Json序列化方式
Java实现序列化的方式有很多种,我平时更喜欢使用通过Json对象实现序列化与反序列化。这里我们需要引入一个Json的工具包
public class Monkey implements Cloneable, Serializable {
private static final String Monkey = null;
private String name;
private String age;
// 职业
private ArrayList<Object> vocation;
@Override
protected Monkey clone() throws CloneNotSupportedException {
Monkey clone = (Monkey) super.clone();
String jsonString = JSON.toJSONString(clone);
Monkey parseObject = JSON.parseObject(jsonString, Monkey.class);
return parseObject;
}
public Monkey() {
super();
// TODO Auto-generated constructor stub
}
public Monkey(String name, String age, ArrayList vocation) {
super();
this.name = name;
this.age = age;
this.vocation = vocation;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public ArrayList getVocation() {
return vocation;
}
public void setVocation(ArrayList vocation) {
this.vocation = vocation;
}
public static String getMonkey() {
return Monkey;
}
}
测试类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<Object> arrayList = new ArrayList<Object>();
arrayList.add(new Object());
Monkey monkey = new Monkey("孙悟空", "三百四十二岁", arrayList);
Monkey clone = monkey.clone();
System.out.println(monkey.equals(clone));
System.out.println(clone.getVocation().equals(monkey.getVocation()));
System.out.println(monkey.getVocation().hashCode());
System.out.println(clone.getVocation().hashCode());
}
}
测试结果:
五、总结
1. 原型模式的设计思路
原型模式的实现思路很简单:
- 继承cloneable接口
- 复写Object类的clone方法
- 业务方调用原型类的clone方法即可
2. 原型模式的特点
优点:
- 提升性能,避免构造函数的约束
缺点:
- 如果类属性中包含引用数据类型需要特殊处理,容易出错
- 必须实现cloneable接口,增加了代码复杂度
好了,今天的内容到此结束,如果还有疑问的同学可以私信我或在评论区留言,我会在第一时间为你解答。觉得有收获的小伙伴请记得一键三连,关注博主不要走丢,拒当白嫖党~