浅谈原型模式

308 阅读7分钟

简单说两句

作者:后端小知识

CSDN个人主页:后端小知识

🔎GZH后端小知识

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

😎原型模式

定义

使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象

角色

  1. 抽象原型类Prototype
  2. 具体原型类 ConcretePrototype

【Tips】:这里我写个简单的demo,主要是体现浅克隆和深克隆的区别

模式类图

image-20230402194522135

举个荔枝

工作中,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切地希望有一种机制能够快速创建相同或者相似的周报。我们通过原型模式来实现

代码

说明:这里就简单模拟下周报的内容,不会真实的存入数据库

我们先上浅克隆的代码

WeekReport.java

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class WeekReport implements Cloneable{
    private String name;
    private int weekNum;
    private String summary;
    private Integer workDays;
    private Date time;

    @Override
    public String toString() {
        return "WeekReport{" +
                "name='" + name + '\'' +
                ", weekNum=" + weekNum +
                ", summary='" + summary + '\'' +
                ", workDays=" + workDays +
                ", time=" + time +
                '}';
    }

    @Override
    public WeekReport clone() {
        try {
            WeekReport clone = (WeekReport) super.clone();
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

Client.java

public class Client {
    public static void main(String[] args) {
        WeekReport wr1 = new WeekReport();
        wr1.setName("孤音");
        wr1.setWeekNum(1);
        wr1.setWorkDays(500);
        wr1.setSummary("暂无额");
        wr1.setTime(new Date());
        System.out.println(wr1);
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        WeekReport wr2 = wr1.clone();
        wr2.setName("哇酷哇酷");
        System.out.println(wr1);
        System.out.println(wr2);
    }
}

运行看结果

WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 19:58:32 CST 2023} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 19:58:32 CST 2023} WeekReport{name='哇酷哇酷', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 19:58:32 CST 2023}

看这个结果,嗯,好像是成功复制了😎

⚠我说的是好像成功复制了,说明可能存在问题额。

image-20230402195629842

别慌,我更改下Client的代码,让你看看哪里有问题

client.java

public class Client {
    public static void main(String[] args) {
        WeekReport wr1 = new WeekReport();
        wr1.setName("孤音");
        wr1.setWeekNum(1);
        wr1.setWorkDays(500);
        wr1.setSummary("暂无额");
        wr1.setTime(new Date());
        System.out.println(wr1);
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        WeekReport wr2 = wr1.clone();
        wr2.setName("哇酷哇酷");
        wr2.getTime().setTime(0);
        System.out.println(wr1);
        System.out.println(wr2);
    }
}

运行看结果

WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 19:59:45 CST 2023} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Thu Jan 01 08:00:00 CST 1970} WeekReport{name='哇酷哇酷', weekNum=1, summary='暂无额', workDays=500, time=Thu Jan 01 08:00:00 CST 1970}

有没有发现问题:我们本来是对克隆的对象进行修改,但是原对象也被跟着修改了💢

what ? 为什么原对象的time也会被跟着修改啊,为什么原对象 name、weekNum等这些属性又没有变呢?

image-20230402200540897

【Tips】这是因为在复制对象时,引用类型成员变量复制的是地址,值类型成员变量复制的是值

就是我们在修改复制出来的对象的引用类型的成员变量时,相当于在修改原对象引用类型成员变量的地址,这叫做浅克隆,这样很显然是不科学的啊

image-20230402201442255

那怎么修改呢? 稍等片刻,我马上给出代码!!!

我们只需要在重写时修改下

    @Override
    public WeekReport clone() {
        try {
            WeekReport clone = (WeekReport) super.clone();
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            Date time = (Date) clone.getTime().clone();
            clone.setTime(time);
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

这样一改就能解决刚才的问题了,我们看看运行结果

WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 20:16:10 CST 2023} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WeekReport{name='孤音', weekNum=1, summary='暂无额', workDays=500, time=Sun Apr 02 20:16:10 CST 2023} WeekReport{name='哇酷哇酷', weekNum=1, summary='暂无额', workDays=500, time=Thu Jan 01 08:00:00 CST 1970}

可以看到,这下在修改复制的对象时,对原对象没有丝毫的影响。

可能有的小伙伴又有疑问了,为什么Date也可以调用clone呢?

嗨呀,这个问题问得好,我们直接点进去看下源码,我直接给你粘贴过来,让你看一下庐山真面目

image-20230402202521279

public class Date
    implements java.io.Serializable, Cloneable, Comparable<Date>
{
    private static final BaseCalendar gcal =
                                CalendarSystem.getGregorianCalendar();
    private static BaseCalendar jcal;

    private transient long fastTime;
    ........
}

看到没有,Date类是实现了Cloneable接口的,也就是说Date是可被克隆的

上面的这也例子就实现了深克隆

OKOK,这下这个问题不仅解决了,疑问也打消了,那么还有没有问题呢?😏

当然有,万一我们的 原型类里面有很多的 引用类型对象 咋办呢?

这好办啊,我们直接一个一个的像刚刚那么处理呗

emmm,好像也可以,不过嘛。。。。。

image-20230402203059412

这代码就不能写优雅点吗?

开个小玩笑,当然可以,谁会去一个一个处理啊,除非😂

处理方法就是我们实现序列化的接口,通过流的形式进行复制(这就和我们平时的 Ctrl c 和 Ctrl v 一样了)

**tips:**也可以采用存文件读文件的方式,但是不怎么通用了,比如你在win上写的代码,你放linux上去就 玩完了

具体的我们来康康代码吧

深克隆的另一种写法

    @Override
    public Object clone() throws CloneNotSupportedException{
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);
            //序列化时,对象的所有属性层级关系会被序列化自动处理
            oos.writeObject(this);
            oos.close();
            //从内存中读取数据
            byte[] bb = outputStream.toByteArray();
            InputStream in = new ByteArrayInputStream(bb);
            ObjectInputStream ois = new ObjectInputStream(in);
            Object clone = ois.readObject();
            ois.close();
            return clone;
        } catch (IOException | ClassNotFoundException e) {
            throw new AssertionError();
        }
    }

累了累了,demo就到这里吧,我们谈谈他的优点😋

模式优点

  1. 简化对象的创建过程,通过复制一个已有实例可以提高创建新实例的效率
  2. 扩展性较好
  3. 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  4. 可以**使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作**

模式缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  2. 实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦

模式适用环境

  1. 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
  2. 系统要**保存对象的状态,而对象的状态变化很小**
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便

【都看到这了,点点赞点点关注呗,爱你们】😚😚

抽象工厂  引导关注

结语

谢谢你的阅读,由于作者水平有限,难免有不足之处,若读者发现问题,还请批评,在留言区留言或者私信告知,我一定会尽快修改的。若各位大佬有什么好的解法,或者有意义的解法都可以在评论区展示额,万分谢谢。 写作不易,望各位老板点点赞,加个关注!😘😘😘

💬

作者:后端小知识

CSDN个人主页:后端小知识

🔎GZH后端小知识

🎉欢迎关注🔎点赞👍收藏⭐️留言📝