创建型-原型模式 ( Prototype Pattern)

开课吧樵夫2022-01-10 15:53

  上一篇咱们学习的是建造者模式,有的小伙伴会觉得比较复杂和难以理解。今天咱们来学习一个简单的设计模式-原型模型。

  什么是原型模式

  原型模式的核心就是克隆对象,将一个对象作为拷贝对象(原型),然后不断的创建出新的对象。

  原型模式的应用场景还是比较多的,例如Spring框架中@Scope("prototype"),项目中数据模型相似的不同业务订单,OA系统中的日报、周报等。

  原型模式 ( Prototype Pattern)

  这里以印钞机为例,印钞机在印刷"软妹币"时,面额和材料相同,但是编号不同,这时候我们就可以使用原型模式。

  第一步:创建软妹币对象并实现Cloneable接口

/**
 * 软妹币对象
 */
public class RMB implements Cloneable {

    private int denomination;

    private int code;

    public RMB() {
        System.out.println("调用软妹币构造函数");
    }

    public int getDenomination() {
        return denomination;
    }

    public void setDenomination(int denomination) {
        this.denomination = denomination;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    @Override
    protected RMB clone() throws CloneNotSupportedException {
        return (RMB) super.clone();
    }

    @Override
    public String toString() {
        return "RMB{" +
                "denomination=" + denomination +
                ", code=" + code +
                '}';
    }
}

  第二步:创建测试类并查看结果

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        RMB rmb1 = new RMB();
        rmb1.setDenomination(100);
        rmb1.setCode(1);

        RMB rmb2 = rmb1.clone();
        rmb2.setCode(2);

        System.out.println("RMB: " + rmb1 + " hashcode:" + rmb1.hashCode());
        System.out.println("RMB: " + rmb2 + " hashcode:" + rmb2.hashCode());
    }
}

  测试结果:

调用软妹币构造函数
RMB: RMB{denomination=100, code=1} hashcode:1239731077
RMB: RMB{denomination=100, code=2} hashcode:557041912

  ps:到这里其实就完成了原型模式的落地了。这里需要注意的是原型对象需要实现Cloneable重写clone方法。如果不实现Cloneable接口但调用super.clone()会抛出异常。这个是Java早期设计上的问题,实现Cloneable只是标记当前类可以使用clone方法而已。

  浅拷贝与深拷贝

  上面软妹币对象中的属性都是简单的数据类型,如果这时候需要再加上操作员的信息呢?咱们来改造下上面的代码看看会出现什么效果。

  第一步:创建操作员

/**
 * 操作员对象 只保留name属性便于演示
 */
public class Operator {

    private String name;

    public Operator() {
    }

    public Operator(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Operator{" +
                "name='" + name + '\'' +
                '}';
    }
}

  第二步:修改软妹币对象

/**
 * 软妹币对象
 */
public class RMB implements Cloneable {

    private int denomination;

    private int code;

    private Operator operator;

    public RMB() {
        System.out.println("调用软妹币构造函数");
    }

    public int getDenomination() {
        return denomination;
    }

    public void setDenomination(int denomination) {
        this.denomination = denomination;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Operator getOperator() {
        return operator;
    }

    public void setOperator(Operator operator) {
        this.operator = operator;
    }

    @Override
    protected RMB clone() throws CloneNotSupportedException {
        return (RMB) super.clone();
    }

    @Override
    public String toString() {
        return "RMB{" +
                "denomination=" + denomination +
                ", code=" + code +
                ", operator=" + operator +
                '}';
    }
}

  第三步:创建测试类并查看结果

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        RMB rmb1 = new RMB();
        rmb1.setDenomination(100);
        rmb1.setCode(1);
        rmb1.setOperator(new Operator("张三"));

        RMB rmb2 = rmb1.clone();
        rmb2.setCode(2);

        System.out.println("RMB: " + rmb1 + " operator:" + rmb1.getOperator().hashCode());
        System.out.println("RMB: " + rmb2 + " operator:" + rmb2.getOperator().hashCode());
    }
}

  测试结果:

调用软妹币构造函数
RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:557041912
RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:557041912

  ps:根据测试结果,我们可以看出operator对象是同一个,这个是因为clone方法使用的是浅拷贝。

  那什么是浅拷贝和深拷贝呢?

  浅拷贝:克隆对象中所有的成员变量与原型对象的值相等,引用类型变量和克隆对象指向同一个对象。

  深拷贝:无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象。

  显然,在很多场景下浅拷贝不满足我们的需求。

  深拷贝实现

  方案一:

  第一步:让Operator实现Cloneable接口

/**
 * 操作员对象 只保留name属性便于演示
 */
public class Operator implements Cloneable {

    private String name;

    public Operator() {
    }

    public Operator(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Operator clone() throws CloneNotSupportedException {
        return (Operator) super.clone();
    }


    @Override
    public String toString() {
        return "Operator{" +
                "name='" + name + '\'' +
                '}';
    }
}

  第二步:重写RMB中的clone方法

/**
 * 软妹币对象
 */
public class RMB implements Cloneable {

    private int denomination;

    private int code;

    private Operator operator;

    public RMB() {
        System.out.println("调用软妹币构造函数");
    }

    public int getDenomination() {
        return denomination;
    }

    public void setDenomination(int denomination) {
        this.denomination = denomination;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Operator getOperator() {
        return operator;
    }

    public void setOperator(Operator operator) {
        this.operator = operator;
    }

    @Override
    protected RMB clone() throws CloneNotSupportedException {
        RMB rmb = (RMB) super.clone();
        rmb.setOperator(this.getOperator().clone());
        return rmb;
    }

    @Override
    public String toString() {
        return "RMB{" +
                "denomination=" + denomination +
                ", code=" + code +
                ", operator=" + operator +
                '}';
    }
}

  第三步:查看测试结果

调用软妹币构造函数
RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:557041912
RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:1134712904

  ps:根据测试结果,operator对象已经是两个对象了。

  方案二:

  第一步:让Operator实现Serializable接口

import java.io.Serializable;

/**
 * 操作员对象 只保留name属性便于演示
 */
public class Operator implements Serializable {

    private String name;

    public Operator() {
    }

    public Operator(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Operator{" +
                "name='" + name + '\'' +
                '}';
    }
}

  第二步:让RMB实现Serializable接口,并自定义深度拷贝方法

import java.io.*;

/**
 * 软妹币对象
 */
public class RMB implements Cloneable, Serializable {

    private int denomination;

    private int code;

    private Operator operator;

    public RMB() {
        System.out.println("调用软妹币构造函数");
    }

    public int getDenomination() {
        return denomination;
    }

    public void setDenomination(int denomination) {
        this.denomination = denomination;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Operator getOperator() {
        return operator;
    }

    public void setOperator(Operator operator) {
        this.operator = operator;
    }

    @Override
    protected RMB clone() throws CloneNotSupportedException {
        return (RMB) super.clone();
    }

    public RMB deepClone() {

        try {
            // 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (RMB) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }


    }

    @Override
    public String toString() {
        return "RMB{" +
                "denomination=" + denomination +
                ", code=" + code +
                ", operator=" + operator +
                '}';
    }
}

  第三步:修改测试类并查看结果

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        RMB rmb1 = new RMB();
        rmb1.setDenomination(100);
        rmb1.setCode(1);
        rmb1.setOperator(new Operator("张三"));

        // 调用深度克隆方法
        RMB rmb2 = rmb1.deepClone();
        rmb2.setCode(2);

        System.out.println("RMB: " + rmb1 + " operator:" + rmb1.getOperator().hashCode());
        System.out.println("RMB: " + rmb2 + " operator:" + rmb2.getOperator().hashCode());
    }
}

  测试结果:

调用软妹币构造函数
RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:863831416
RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:1836643189

  ps:根据测试结果,operator对象也是两个不同的对象。

  总结

  优点:

  用法简单,上手难度低

  提高创建新对象的效率

  可实现不同时刻对象状态(如导入昨天日报)

  缺点:

  必须要实现Cloneable接口重写clone方法

  违背开闭原则

  深拷贝实现相对复杂

  引用类型如果是多层嵌套,则每一层对象都需要支持克隆

  以上就是开课吧小编为大家整理发布的“创建型-原型模式 ( Prototype Pattern)”一文,更多相关内容尽在开课吧广场Java教程频道。

免责声明:本站所提供的内容均来源于网友提供或网络搜集,由本站编辑整理,仅供个人研究、交流学习使用。如涉及版权问题,请联系本站管理员予以更改或删除。
有用
分享