创建型模式丨原型模式

首页 / 默认分类 / 正文

前言

原型模式(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 引用指向的不是同一个对象。这就是深克隆。

优点与缺点

优点:

  1. 可以克隆对象,而无需与它们所属的具体类相耦合。
  2. 可以克隆预生成原型,避免反复运行初始化代码。
  3. 可以更方便地生成复杂对象,在性能上比直接 new 一个对象好。
  4. 可以用继承以外的方式来处理复杂对象的不同配置。

缺点:

  1. 需要为每一个类都配置一个 clone 方法。
  2. 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,这会使此模式变得异常麻烦。

    参考

    深入设计模式
    LeetBook - 深入浅出设计模式
打赏
评论区
头像
文章目录