前言
原型模式(Prototype Pattern
),它主要用于对象的复制。
定义与条件
定义:用一个已经创建的对象作为原型实例,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
使用条件:
- 实现
Cloneable
接口。在 java 语言有一个 Cloneable 接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用 clone 方法。在 java 虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException 异常。 重写 Object 类中的 clone 方法。Java 中,所有类的父类都是 Object 类,Object 类中有一个 clone 方法,作用是返回对象的一个拷贝,但是其作用域 protected 类型的,一般的类无法调用,因此,Prototype 类需要将 clone 方法的作用域修改为 public 类型。
适用场景
例如:如果你想得到两个相同的对象,一般来说可以这么做:先新建(
new
)一个属于相同类的对象,然后你遍历(for
)原始对象的所有成员变量,再将成员变量值复制(set
)到新对象中。
但是,并非所有的对象都可以使用这种直接的复制,你可能会遇到以下的问题:- 你需要知道对象所属的类才能新建复制对象,所以代码必须依赖该类。
- 因为有些对象可能拥有私有成员变量,它们在对象本身以外是不可见的,你需要繁琐的数据准备或访问权限等。
- 创建对象成本较大,例如初始化时间长,占用 CPU 太多,或者占用网络资源太多等。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值,那么将会出现大量重复的代码。
原型模式将克隆过程委派给被克隆的实际对象。模式为所有支持克隆的对象声明了一个通用接口,该接口让你能够克隆对象,同时又无需将代码和对象所属类耦合。
模式的结构
原型模式的主要角色如下:
- 抽象原型类(Prototype):规定了具体原型对象必须实现的接口,大多数情况下,其中只会有一个名为 clone 的克隆方法。
- 具体原型类(Concrete Prototype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 客户端(Client):使用具体原型类中的 clone() 方法来复制新的对象。
编码实现
例如,某一天你去奶茶店偶遇周杰伦,你发现他点了一杯半糖常规冰的超大杯奶茶,于是你也要来一份周杰伦同款奶茶,后面闻风而来在后排队的粉丝们也要同款,如果用常规的复制,就需要 new 新建 N 次,并为每一个新的奶茶对象赋值 N 次,造成大量的重复。
所以运用原型模式:
package designPattern.prototype;
/**
* @author yyt
* @date 2021年01月19日 17:06
*/
public class MilkTea implements Cloneable{
// 奶茶名
private String name;
// 是否全糖
private boolean totalSugar;
// 是否加冰
private boolean ice;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isTotalSugar() {
return totalSugar;
}
public void setTotalSugar(boolean totalSugar) {
this.totalSugar = totalSugar;
}
public boolean isIce() {
return ice;
}
public void setIce(boolean ice) {
this.ice = ice;
}
@Override
public String toString() {
return "MilkTea{" +
"名称='" + name + '\'' +
", 是否全糖=" + totalSugar +
", 是否加冰=" + ice +
'}';
}
@Override
protected MilkTea clone() throws CloneNotSupportedException {
return (MilkTea)super.clone();
}
}
测试方法:
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 下单
order();
}
private static void order() throws CloneNotSupportedException {
MilkTea milkTeaOfJay = new MilkTea();
milkTeaOfJay.setName("原味珍珠奶茶");
milkTeaOfJay.setTotalSugar(false);
milkTeaOfJay.setIce(true);
System.out.println("JayChou Drink MilkTea" + milkTeaOfJay);
// 复制
MilkTea yourMilkTea = milkTeaOfJay.clone();
System.out.println("Your MilkTea" + yourMilkTea);
// 判断
System.out.println(milkTeaOfJay == yourMilkTea);
}
}
原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
浅克隆
需要注意的是,Java 自带的 clone 方法是浅克隆——仅仅复制调用的对象,并不会复制所引用的对象。以老师和学生的关系为例:
测试方法:
public class ShallowClone {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
teacher.setName("汤家凤");
teacher.setAge(40);
// 学生
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(20);
stu1.setHeadmaster(teacher);
// 复制
Student stu2 = (Student) stu1.clone();
System.out.println("===拷贝得到的stu2===");
System.out.println(stu2.toString());
System.out.println("stu1 和 stu2 是同一个人吗?" + (stu1 == stu2));
// 修改老师信息
teacher.setAge(45);
System.out.println("===修改老师的信息后===");
System.out.println(stu1.getHeadmaster().getAge());
System.out.println(stu2.getHeadmaster().getAge());
}
}
从运行结果来看,stu1 和 stu2 并不是同一个学生,但他们的班主任都是同一个老师,说明他们的 Teacher 引用指向的是同一个对象。这就是浅克隆。
深克隆
深克隆会拷贝所有的属性,并且会拷贝属性指向的动态分配的内存。因为把要复制的对象所引用的对象也复制了,所以相对浅克隆来说,深克隆的速度较慢且花销较大。
将上述的例子修改以下:Teacher 类实现 clone 方法,Student 类中修改 clone 方法的逻辑。
测试方法:
public class ShallowClone {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
teacher.setName("汤家凤");
teacher.setAge(40);
// 学生
Student stu1 = new Student();
stu1.setName("小明");
stu1.setAge(20);
stu1.setHeadmaster(teacher);
// 复制
Student stu2 = (Student) stu1.clone();
System.out.println("===拷贝得到的stu2===");
System.out.println(stu2.toString());
// 修改老师信息
teacher.setAge(18);
teacher.setName("张三");
System.out.println("===修改老师的信息后===");
System.out.println(stu1.getHeadmaster());
System.out.println(stu2.getHeadmaster());
}
}
从运行结果来看,对 teacher 对象的修改只能影响 stu1 对象,说明 stu1 和 stu2 中的 teacher 引用指向的不是同一个对象。这就是深克隆。
优点与缺点
优点:
- 可以克隆对象,而无需与它们所属的具体类相耦合。
- 可以克隆预生成原型,避免反复运行初始化代码。
- 可以更方便地生成复杂对象,在性能上比直接 new 一个对象好。
- 可以用继承以外的方式来处理复杂对象的不同配置。
缺点:
- 需要为每一个类都配置一个 clone 方法。
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,这会使此模式变得异常麻烦。
参考
深入设计模式
LeetBook - 深入浅出设计模式