设计模式之原型模式

前言

设计模式一直都是我们程序语言中较为重要的代码优化套路

为了减少一些冗余代码,提高代码可复用性、可维护性、可读性、稳健性以及安全性。
设计是一种语法规范,是一种为了解决某种特点场景下的某个问题,而提出来的一系列方案。
设计模式一共分为23种,其中分为三种大类型;而我们今天所有讲的是创造型中的原型模式

介绍

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它提供了一种创建对象的最佳方式。
想要使用这种模式需要实现一个接口 Cloneable接口,并重写clone方法,此方法作用是创建一个
调用此方法的对象的副本,就相当于复制一份,但是拷贝的是原型对象的二进制,拷贝完之后的副本对象和原型对象的地址值是不一致的!!!

代码实现浅克隆

定义一个奶牛类,并实现Cloneable 
public class Milk implements Cloneable {
  private String name;
    private String color;
    private int milliliter;
    private String shapesOfPack;
    private Cow cow;

    public Milk() {
    }

    public Milk(String name,String color, int milliliter, String shapesOfPack, Cow cow) {
        this.name = name;
        this.color = color;
        this.milliliter = milliliter;
        this.shapesOfPack = shapesOfPack;
        this.cow = cow;
    }
    ....包含getset方法

  @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    重写了父类中的方法,此方法调用了一个本地方法,也就是c++的方法
定义一个奶牛类

public class Cow  {
    private String name;

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

    public String getName() {
        return name;
    }

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

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

测试代码

  //创建纯正特仑苏
        Milk milk = new Milk();

        //创建特仑苏的奶牛
        Cow cow = new Cow("普通奶牛");
        milk.setName("特仑苏");
        milk.setColor("White");
        milk.setMilliliter(250);
        milk.setShapesOfPack("正方形");
        milk.setCow(cow);
        /**
         * 经过制作之下,特仑苏销量一飞冲天,此时有不法分子,仿造特仑苏,也做了一款牛奶
         */
        Milk cloneMilk = (Milk) milk.clone();//浅克隆
        //特仑苏总部发现之后,买来比较
        System.out.println(milk.getClass());
        System.out.println(cloneMilk.getClass());
        System.out.println(milk);
        System.out.println(cloneMilk);
        /**
         * 特仑苏总部发现原料一模一样,于是他们就更换了奶牛
         */
        milk.getCow().setName("蒙牛");
        System.out.println(milk);
        System.out.println(cloneMilk);
        System.out.println(cloneMilk.getCow()==milk.getCow());
     

运行结果
运行结果,不难发现,我修改了原型对象属性中的cow名字,但是副本对象也会跟着修改

浅克隆的缺点

在这里插入图片描述

 *  浅克隆缺点暴露,
         *  浅克隆只拷贝了基本类型和String类型的数据值,但是对于引用类型,浅克隆直接复制了地址值
         *  这就导致原型对象和副本对象的属性都适用了同一片内存空间,那么当有一方修改了值之后,另外一方也会自动修改
    解决思路: 我们可以再clone方法中将cow对象重新克隆,这样他们就不会在共享同一片地址

深克隆代码实现

直接将原型对象中依赖的cow对象克隆一份(克隆之后的对象地址不一致)

   protected Object clone() throws CloneNotSupportedException {
        /** ----------->深度克隆场景
         * 特仑苏总部发现每当他们更换原材料的时候,盗版总会跟随更改,
         * 那么这个时候他们决定买断蒙牛,只为特仑苏提供原材料,这个时候盗版集团没有办法只能用蒙牛的盗版母牛来做原材料
         *
         * 实质就是将引用变量重新克隆了一个,使其地址值不一致
         */
      
        Milk cloneMilk = (Milk)super.clone();
        Cow clone1 = (Cow)cloneMilk.getCow().clone();//盗版奶牛
        cloneMilk.setCow(clone1);

输出结构

在这里插入图片描述

   那么这个就是我们的深克隆,但是我们需要思考一个问题,这样的深克隆代码有缺点吗?
   假如:原型对象中的属性深度(属性是引用类型,且内部又嵌套了对象)较高得情况下,我们需要实现
         n多类的方法,显然这是不合理的,所以呢,我们可以使用序列化与反序列化来实现深克隆

在这里插入图片描述

  Milk milk=null;
        try {
            OutputStream fos = new FileOutputStream("F:\\a.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(this);
            InputStream fis = new FileInputStream("F:\\a.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
             milk = (Milk) ois.readObject();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

这样实现的好处是无论你原型对象深度多高,那么在反序列化回来的时候,对象都是不一致的,
但是这样还是有缺点的,我们在类中标明了盘符,这就表示与我们window系统有强耦合关系
如果我需要在linux下实现克隆怎么办??

所以最好的办法是将对象存储到jvm内存中,那么不管你是什么系统,也可以进行
ByteArrayOutputStream流是一个二进制流,可以将对象存取到内存中
ByteArrayIntpuStream 可以将对象从内存中读取出来

  ByteArrayOutputStream fos = null;
        Milk milk=null;
        try {
            fos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(this);//将内存中的对象存储
            byte[] bytes = fos.toByteArray();

            InputStream fis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(fis);
            milk = (Milk) ois.readObject();//将内存中的对象取出来
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

那么到现在,我们一整个深克隆的代码优化已经全部完成了

    深克隆与浅克隆的区别
       浅克隆:在克隆原型对象的时候会将基本类型和String类型数据的值进行拷贝,但是对于引用类型数据
                  浅克隆是直接拷贝对象的内存地址,这就导致原型对象和副本对象的属性使用了同一片内存空间
       深克隆:在克隆原型对象的时候会将基本类型和String类型数据的值进行拷贝,且对于引用类型数据,
              也会重 新拷贝一份数据,而不是直接使用原本的地址值,这样原型对象和副本对象的属性就不会使用同一片空间

注意:序列化需要实现Serializable
          克隆需要实现Cloneable
  场景描述:
(1)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
(2)通过new一个对象需要非常繁琐的数据准备或访问权限,可以使用原型模式。
(3)一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝

还有一点:克隆会破坏单例哦,这是除了反射和反序列化之后出现的第三种破坏单例的模式

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值