原型模式

为君一笑 提交于 2020-02-06 07:36:43

问题描述

现在有一只羊tom,姓名为tom,年龄为1,颜色为白色,请编写程序创建和tom属性完全相同的10只羊

传统思维

直接new出一个对象
优点:比较好理解,简单易操作
缺点:

  • 在创建新的对象的时候,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率低。
  • 总是需要重新初始化对象,而不是动态的对象运行时的状态,不够灵活

使用原型模式

概括

  • 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式。允许一个对象再创建另一个可制定对象,无需知道如何创建的细节
  • 工作原理:通过一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实现创建,即 对象.new()
  • 思路:利用Object超类的clone()方法,可以将对象复制一份,但是实现clone()的java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制能力。
  • 缺点:如果需要对一个类配备一个克隆方法,这对全新的类不难,但对已有的类进行改造的时候,需要修改源代码,违背了OCP(开闭)原则。
  • 优点:简化对象创建过程,同时也能够提高效率;不用重新初始化对象,而是动态获取对象运行时状态;但对象发生变化时,克隆对象也会发生变化,无需修改代码。

实现

创建可以clone的类,实现Cloneable接口,显示重写clone方法

public class Sleep implements Cloneable{
    private String name;
    private int age;
    private String color;
    //构造器
    public Sleep(String name, int age, String color){
        super();
        this.name = name;
        this.age = age;
        this.color = 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;
    }

    @Override
    public String toString() {
        return "Sleep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
    //显示实现clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sleep sleep = null;
        try {
            sleep = (Sleep) super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return sleep;
    }
}

实现克隆

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sleep sleep = new Sleep("tom",1,"白色");
        Sleep sleep1 = (Sleep) sleep.clone();
        Sleep sleep2 = (Sleep) sleep.clone();
        Sleep sleep3 = (Sleep) sleep.clone();
        Sleep sleep4 = (Sleep) sleep.clone();
        Sleep sleep5 = (Sleep) sleep.clone();
        System.out.println(sleep1+ "" +sleep1.hashCode());
        System.out.println(sleep2+ "" +sleep2.hashCode());
        System.out.println(sleep3+ "" +sleep3.hashCode());
        System.out.println(sleep4+ "" +sleep4.hashCode());
        System.out.println(sleep5+ "" +sleep5.hashCode());

    }
}

使用原型模式的时候如果原型发生变化,不需要改变其他的属性,简化代码。

原型模式在Spring框架中源码分析

  • Spring中原型bean的创建,就是原型模式(多例)
    <bean id = "id01" class="com.retemin.spring.bean.linsir" scope="prototype">

原型模型的深拷贝问题

当被拷贝的对象包含其他引用类型对象的时候,克隆的时候不会复制所包含的引用对象,而是将原对象中的引用对象的地址复制了一份,没有创建新的引用对象。这是一种浅拷贝。
代码示例,sleep中包含引用对象 friend

  public Sleep friend;//引用对象

对sleep进行拷贝

public static void main(String[] args) throws CloneNotSupportedException {
        Sleep sleep = new Sleep("tom",1,"白色");
        sleep.friend = new Sleep("jack",1,"白色");
        Sleep sleep1 = (Sleep) sleep.clone();
        Sleep sleep2 = (Sleep) sleep.clone();
        Sleep sleep3 = (Sleep) sleep.clone();
        Sleep sleep4 = (Sleep) sleep.clone();
        Sleep sleep5 = (Sleep) sleep.clone();
        System.out.println(sleep1+ "" +sleep1.hashCode() +"  "+sleep1.friend.hashCode());
        System.out.println(sleep2+ "" +sleep2.hashCode() +"  "+sleep2.friend.hashCode());
        System.out.println(sleep3+ "" +sleep3.hashCode() +"  "+sleep3.friend.hashCode());
        System.out.println(sleep4+ "" +sleep4.hashCode()+"  "+sleep4.friend.hashCode());
        System.out.println(sleep5+ "" +sleep5.hashCode()+"  "+sleep5.friend.hashCode());
    }    

得到的结果,所引用对象的hashcode是一样的

Sleep{name='tom', age=1, color='白色', fridend='1735600054'}21685669  1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}2133927002  1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}1836019240  1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}325040804  1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}1173230247  1735600054

结论

1、浅拷贝的介绍:对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将属性复制一份给新的对象
2、对于数据类型是引用类型的成员变量,比如说数据,某个类,浅拷贝会进行引用传递,也就是说只是内存地址的拷贝,不会拷贝实际的值,因此拷贝后的对象一旦发生改变,所拷贝的所有地方的引用对象都会发生改变
3、浅拷贝使用默认的clone()方法来实现
sleep = (Sleep)super.clone()

深拷贝介绍
1、复制的对象是所有基本类型的成员变量值
2、为所有引用数据类型的成员变量申请存储空间,并复制每一个引用数据类型成员变量所引用的对象,深拷贝是对整个对象的拷贝,而不只是引用(内存地址)的拷贝
3、实现方式1:重写clone方法实现;2:通过对象序列化实现深拷贝

深拷贝案例

创建一个被引用对象

 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;
    }
    //没有引用类型的成员变量,使用默认的clone进行浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

创建一个引用对象包含引用对象,通过两种方式进行深拷贝,重写clone方法和序列化

public class DeepPrototype implements Serializable,Cloneable {
    public  String name;
    public DeepCloneableTarget deepCloneableTarget;
    public DeepPrototype(){
        super();
    }
    //深拷贝1,修改clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        deep = super.clone();
        //对引用的类型进行单独处理
        DeepPrototype deepPrototype = (DeepPrototype) deep;
        deepPrototype.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
        return deepPrototype;
    }
    //深拷贝2,通过对象序列化实现(推荐)
     public Object deepClone(){
        //创建流对象
         ByteArrayOutputStream bos = null;
         ObjectOutputStream oos = null;
         ByteArrayInputStream bis = null;
         ObjectInputStream ois = null;
         try{
             //序列化
             bos = new ByteArrayOutputStream();
             oos = new ObjectOutputStream(bos);
             oos.writeObject(this);
             //反序列化
             bis = new ByteArrayInputStream(bos.toByteArray());
             ois = new ObjectInputStream(bis);
             DeepPrototype deepPrototype = (DeepPrototype) ois.readObject();
             return deepPrototype;
         } catch (IOException e) {
             e.printStackTrace();
             return null;
         } catch (ClassNotFoundException e) {
             e.printStackTrace();
             return null;
         }
         finally {
              try{
                  ois.close();
                  bis.close();
                  oos.close();
                  bos.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
         }
     }
}

测试

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype deepPrototype = new DeepPrototype();
        deepPrototype.name = "retmein";
        deepPrototype.deepCloneableTarget = new DeepCloneableTarget("测试","类");
        //方式1进行深拷贝
        DeepPrototype deepPrototype1 = (DeepPrototype) deepPrototype.clone();
//        DeepPrototype deepPrototype1 = (DeepPrototype)deepPrototype.deepClone();
        //打印结果,如果引用对象的hashcode不一样,可以说明深拷贝完成
        System.out.println("deepPrototype.name= "+ deepPrototype.name+" deepPrototype.deepCloneableTarget="+deepPrototype.deepCloneableTarget.hashCode());
        System.out.println("deepPrototype1.name= "+ deepPrototype1.name+" deepPrototype.deepCloneableTarget="+deepPrototype1.deepCloneableTarget.hashCode());
    }
}

两种方式的结果

deepPrototype.name= retmein deepPrototype.deepCloneableTarget=1735600054
deepPrototype1.name= retmein deepPrototype.deepCloneableTarget=21685669

可以看到DeepPrototype的引用对象变量的hashcode不同,可以知道深拷贝成功。

分析两种深拷贝方式

重写clone方法,需要重新创建一个所引用的对象,当修改的时候需要修改clone方法,计较麻烦。推荐使用序列化的方法。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!