设计模式之原型模式

原型模式

  1. 原型模式基本介绍
  2. 举例和演示
  3. 深浅拷贝的介绍
  4. 原型模式的总结

1.原型模式基本介绍

  • 原型模式是指: 采用原型实例创建对象的种类,并且通过拷贝这些模型创建新的对象

  • 原型模式是一种创建性模式,允许一个对象以自身为模板,创建一个可定制的对象,无需知晓如何创建的细节

  • 具体实现: 实现Cloneable接口,重写或者直接使用clone方法

2.举例和演示

假设现在存在一个类,然后得到若干个这个类的复制体,如何实现???

1. 原始方案: 采用在构造函数内赋值的方法
  • 代码演示:

```java
package com.liz.GOF23.prototype.origin_way;

public class Sheep {
    private String name;
    private int age;
    private String color;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}
public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "white");
        //.... copy
        //在构造函数内部进行“复制”
        Sheep sheepCopy1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        System.out.println(sheepCopy1);
        //.... show
    }

谈谈优缺点
  • 优点:比较好理解,简单易操作
  • 缺点:创建新对象的时候需要重新获取原始对象的属性,如果创建对象比较复杂,效率较低,同时不能动态获取对象的运行状态,不够灵活。
2.改进方案
  • 使用原型模式
    java的Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,但是需要实现接口Cloneable(该接口表示该类可以复制,并且具有复制的能力),同时调用clone方法

  • 代码演示

package com.liz.GOF23.prototype.clone_way;

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    public  Sheep friend;//是对象,克隆会如何处理,默认是浅拷贝
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
}

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
    //克隆该实例

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = null;//待克隆对象
        sheep=(Sheep)super.clone();
        return sheep;
    }
}

`public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        System.out.println("使用原型模式完成对象的创建");
        Sheep sheepModel = new Sheep("tom",1,"白色");
        //原始方法
        sheepModel.friend = new Sheep("tom_friend",2,"黑色");

        //开始克隆(默认是浅拷贝)
        Sheep sheep2 = (Sheep) sheepModel.clone();
        Sheep sheep3 = (Sheep) sheepModel.clone();
        //System.out.println(sheepModel.friend);
        //对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组,某个类的对象等。那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象(hashcode一致)
        System.out.println("sheep2: "+sheep2+" sheep2:friend"+sheep2.friend.hashCode());
        System.out.println("sheep3: "+sheep3+" sheep3:friend"+sheep3.friend.hashCode());
    }
}
  • 运行结果
    在这里插入图片描述
  • 注: 在这里两个“复制体” sheep2,sheep3的引用属性friend的hashcode一致,说明系统提供的clone方法是浅拷贝(引用类型指向同一个引用,hashcode一致)

深浅拷贝的介绍

浅拷贝特性
  • 对于数据类型是基本数据类型的成员变量,浅拷贝将该属性值复制一份给新的变量
  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组,某个类的对象等,那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象【即两个引用会指向同一个对象】。
  • 多个对象指向堆中的同一个实例,在这种情况下,一个对象去修改该成员变量会影响到另一个对象。
  • 代码实现:
    上述的原型模式使用的就是浅拷贝,因此两个sheep的friend(引用类型)指向堆中的同一个实例(hashcode一致)
sheep = (Sheep) super.clone();// 浅拷贝是使用默认的clone()实现
深拷贝特性
  • 对于数据类型是基本数据类型的成员变量,深拷贝和浅拷贝一致,进行属性值的重新复制
  • 对于数据类型是引用数据类型的成员变量,不再进行内存地址的传递,而是在堆中重新开辟一块空间,存放该对象实例的复制,然后被“复制体”所引用
    简单示意图...
  • 如何实现深拷贝
    • 重写clone方法(即对出现存在引用类型,单独对引用类型进行处理)【类似剥洋葱,给引用类型加上clone方法,直到该类没有引用类型为止】

    • 采用序列化和反序列化的方式进行深拷贝

      • 代码演示:
//引用实例
public class DeepCloneableTarget implements Serializable, Cloneable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private String cloneName;
    private String cloneClass;

    //构造器
    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可

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




//待复制的实例
//Serializable 序列化(将对象的信息转化为可以存储或者传输的过程)
public class DeepProtoType implements Serializable, Cloneable {
    public String name;//String 属性
    public DeepCloneableTarget deepCloneableTarget;//引用类型(自定义clone需要改造这个引用类)

    public DeepProtoType() {
        super();
    }
//浅拷贝
//    @Override
//    protected Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }


    //深拷贝实现1 重写clone
    //对引用类型一层一层进行向下复制 直到出现没有引用类型即可(此时需要去给DeepCloneableTarget 加上clone方法)

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //完成对基本数据类型(属性)和String的克隆
        Object deep = null;
        deep = super.clone();

        //对引用类型的属性进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType) deep;
        //!!! 引用类型的clone(该引用类型内部全部为String 无其他引用实例)
        deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone(); //!!!! 由于这里实现了clone方法
        return deepProtoType;
    }
//-------------------------------------------------------------------------------------
    //深拷贝实现2 通过对象的序列化和反序列化实现深拷贝(!! 推荐使用)
    public Object deepClone() throws IOException {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            //开始序列化操作
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);//将当前对象以对象流的形式进行输出(序列化)

            //开始反序列化操作
            //bis读出了bos的信息 相当于克隆
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepProtoType cloneDemo = (DeepProtoType) ois.readObject();
            return cloneDemo;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
       finally {
            bos.close();
            oos.close();
            bis.close();
            ois.close();
        }
        return null;
    }




//结果演示
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException, IOException {
        DeepProtoType dpt = new DeepProtoType();
        dpt.name = "1z";
        dpt.deepCloneableTarget = new DeepCloneableTarget("box","Box");

        /**
         * 自定义: 深拷贝
         * 默认:   浅拷贝
         * */
        //方案1 重写clone
        //DeepProtoType dptClone = (DeepProtoType) dpt.clone();

        //方案2 对象的序列化和反序列化
        DeepProtoType dptClone = (DeepProtoType)dpt.deepClone();

        //比较引用类型的hashcode
        //!!! 在这里hashcode不一样 表示深拷贝成功
        System.out.println(dpt.deepCloneableTarget.hashCode());
        System.out.println(dptClone.deepCloneableTarget.hashCode());


    }
}
  • 深拷贝代码运行结果:
    hashcode的值不同,表示深拷贝实现了引用类型不再进行内存地址的传递,而是在堆中重新开辟一块空间,存放该对象实例的复制,然后被“复制体”所引用

    在这里插入图片描述

!!! 推荐使用序列化和反序列化的方式,进行深拷贝

原型模式的总结

  • 当创建对象比较复杂时,可以利用原型模式简化对象的创建过程,同时提高效率
  • 原型模式可以动态获得对象的状态,如果修改原型对象的属性,复制对象自动进行修改属性
  • 原型对象同样存在缺点: 如果对已有的类进行原型模式修改的时候,需要修改源代码,违反了OCP(开放封闭原则),所以在设计时就要考虑是否该实例需要复制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值