美文网首页
设计模式——原型模式

设计模式——原型模式

作者: Qiansion齐木楠雄 | 来源:发表于2021-05-19 17:51 被阅读0次

问题

在OA系统中,有一个员工提交周报的功能。每一个周五,员工都要总结本周工作内容,以及写下周工作计划。问题是,每一周的周报,内容上基本都是大同小异。出现特殊情况的概率很小。
周报的表单看起来是这个样子的:


report.png

场景再现

getter setter toString方法请自动生成

class Report {
    private int id;
    private String emp;
    private String summary;
    private String plan;
    private String suggestion;
    private Date time;
}
public static void main(String[] args) {
        //第一周周报
        Report report = new Report();
        report.setEmp("张三");
        report.setSummary("吃饭睡觉");
        report.setPlan("划水");
        report.setSuggestion("无");
        report.setTime(new Date());

        //第二周周报,问题是,尽管第二周周报大部分内容和第一周相同,但是仍然要重复设置
        //等同于在表单中重复填写与上周同样的内容(我们希望的是只设置变化的部分)
        Report report2 = new Report();
        report2.setEmp("张三");
        report2.setSummary("吃饭2睡觉2");
        report2.setPlan("划水2");
        report2.setSuggestion("无");
        report2.setTime(new Date());

        System.out.println(report);
    }

现在希望只修改变化的部分

解决办法

使Report类实现Cloneable接口(一般带able的接口都是一种标记接口,没有具体的实现),表示一种能力,告诉jvm它是可以被克隆的。重写父类Object的clone方法,这里将protected改为了public

@ToString
@Getter
@Setter
class Report implements Cloneable{
    private int id;
    private String emp;
    private String summary;
    private String plan;
    private String suggestion;
    private Date time;
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

这样完成了复制的能力,如此一来,我们在复制的基础上进行修改即可。一般叫做克隆,此处为浅克隆

 public static void main(String[] args) throws CloneNotSupportedException {
        //第一周周报
        Report report = new Report();
        report.setEmp("张三");
        report.setSummary("吃饭睡觉");
        report.setPlan("划水");
        report.setSuggestion("无");
        report.setTime(new Date());


        //第二周周报,问题是,尽管第二周周报大部分内容和第一周相同,但是仍然要重复设置
        //等同于在表单中重复填写与上周同样的内容(我们希望的是只设置变化的部分)
        Report report2 = (Report) report.clone();
        report2.setSummary("吃饭2睡觉2");
        report2.setPlan("划水2");
        report2.setTime(new Date());
        System.out.println(report);
        System.out.println(report2);
    }

Report(id=0, emp=张三, summary=吃饭睡觉, plan=划水, suggestion=无, time=Wed May 19 10:39:10 CST 2021)
Report(id=0, emp=张三, summary=吃饭2睡觉2, plan=划水2, suggestion=无, time=Wed May 19 10:39:10 CST 2021)

总结

使用原型模式来解决该问题
1、必须让目标类实现Cloneable接口,该接口中没有任何抽象方法。这样的接口仅仅是一个“标记接口”。作用是,告诉虚拟机任何实现了Cloneable接口的对象,都可以被克隆
2、必须重写java.lang.Object的clone方法。

思考

1、clone方法,会不会引起构造器调用?
不会。(请自行添加构造方法进行尝试,此处已验证)
2、clone方法是如何实现克隆对象的效果呢?
直接赋值内存中的2进制,效率更高
3、克隆出来的对象和原先的对象,地址是否一致?
不一致,复制到内存的另一片区域

其实细心的朋友在此时应该会发现,在上面"浅拷贝"的例子中,report2在setTime时,也把report的时间改掉了。以至于在输出的时候他们的时间是一样的。它们都是各自设置的时间,但是在控制台却是一样的,那为什么只有时间相同呢,不是也修改了summary和plan吗?这里就要知道“浅拷贝”到底是如何拷贝的,见下图

浅克隆.png
对于引用类型来说,浅拷贝是直接将引用的地址传递过去,所以,当其中任意一个修改了实际对象,都会对对方造成影响。这里可能有人会问String不也是引用类型吗?为什么String没有受到影响呢?这是由于String本身的特殊性。详细过程会专门出一篇文章来讲解。本例的过程如下。由于是内存中直接复制使得时间的引用都指向同一块内存地址(Addr:200),所以上面控制台输出的时间永远都是一样的。
浅克隆实例.png
显然浅克隆不满足实际的需求,我们希望的是,克隆出来的副本,无论怎么修改它,都不会影响原来的对象

改进1

通过前面的两张“浅克隆*”的图片,我们可以知道,问题就出在引用类型Time这里,因为本体和副本的Date都指向了内存地址为200的地方。只要此处不指向同一个内存地址,那么就不会相互影响了。


深克隆.png
image.png

所以说,羊毛出在羊身上,既然是克隆出现了问题,我们就修改克隆的方法

    @Override
    public Object clone() throws CloneNotSupportedException {
        Report report= (Report) super.clone();
        Date cloneTime = (Date) report.getTime().clone();
        report.setTime(cloneTime);
        return report;
    }

再次执行main方法,时间已经是不一样了。
Report(id=0, emp=张三, summary=吃饭睡觉, plan=划水, suggestion=无, time=Wed May 19 17:04:06 CST 2021)
Report(id=0, emp=张三, summary=吃饭2睡觉2, plan=划水2, suggestion=无, time=Thu Jan 01 08:00:00 CST 1970)
但是这种方法也带来相应的问题。此处的例子比较简单,只是一个时间的引用而已。如果是一个多层嵌套的引用呢?如果有一个公司类,公司里包含部门,部门里面又包含人员,人员包含工资之类等等。每次都要一个个的去克隆吗?

改进2

此时,救命的药来了——序列化。经过序列化和反序列化之后的对象是两个不同的对象。如此一来,我们就不需要进行克隆操作,直接进行序列化操作,因为序列化本身就是深克隆。直接在内存里进行序列化,然后再从内存中读出即可。

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(out);
            // 序列化时,对象的所有层级关系会被序列化自动处理
            oos.writeObject(this);
            oos.close();

            //从内存中读取
            byte[] bb = out.toByteArray();
            System.out.println("内存地址为:"+bb);

            InputStream in = new ByteArrayInputStream(bb);
            ObjectInputStream ois = new ObjectInputStream(in);
            Object clone = ois.readObject();
            ois.close();

            return clone;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

执行main方法
内存地址为:[B@5e481248
Report(id=0, emp=张三, summary=吃饭睡觉, plan=划水, suggestion=无, time=Wed May 19 17:39:03 CST 2021)
Report(id=0, emp=张三, summary=吃饭2睡觉2, plan=划水2, suggestion=无, time=Thu Jan 01 08:00:00 CST 1970)

相关文章

网友评论

      本文标题:设计模式——原型模式

      本文链接:https://www.haomeiwen.com/subject/vadyjltx.html