java & 设计模式 & 原型模式

125 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

什么是原型模式

原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型从而创建新的对象。 原型模式是一种创建行设计模式,允许一个对象在创建另一个可定制的对象,无需知道如何创建的细节 工作原理是:是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们来实现创建的对象,clone() 方法

克隆羊问题

创建10个一摸一样的 羊

首先我们准备一个实体类 并实现 Cloneable 重写clone() 方法

@Data
public class Sheep implements Cloneable{
    private String name;
    private String localhost;
    private Sheep son;

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

使用 Cloneable的clone进行拷贝 (浅拷贝)

public class Test001 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep();
        sheep.setName("jake");
        sheep.setLocalhost("北京");
        Sheep sheep1 = new Sheep();
        sheep1.setName("jake son");
        sheep.setSon(sheep1);
        Sheep clone1 = (Sheep) sheep.clone();
        Sheep clone2 = (Sheep) sheep.clone();
        Sheep clone3 = (Sheep) sheep.clone();
        Sheep clone4 = (Sheep) sheep.clone();
        System.out.println("clone1 = " + clone1+"--son hashCode:"+clone1.getSon().hashCode());
        System.out.println("clone2 = " + clone2+"--son hashCode:"+clone2.getSon().hashCode());
        System.out.println("clone3 = " + clone3+"--son hashCode:"+clone3.getSon().hashCode());
        System.out.println("clone4 = " + clone4.hashCode()+"--son hashCode:"+clone4.getSon().hashCode());
    }
}

输出结果:

clone1 = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258
clone2 = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258
clone3 = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258
clone4 = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258

可以看到他的引用对象Son 其实并没有去复制而是指向第一个对象的引用对象也就是浅拷贝造成的。 浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值的传递,也就是将该属性值复制一份给新的对象。
  • 对于数据类型是引用数据类型的成员变量,比如成员变量是某个数组,或者对象等,那么浅拷贝只是会将引用对象的 内存地址辅助一份给新的对象。实际中后面克隆的对象进行引用对象修改后会影响到所有克隆对象的引用对象值。

就如下所示:我们只是在clon2 中修改son的属性

public static void main(String[] args) throws CloneNotSupportedException {
    Sheep sheep = new Sheep();
    sheep.setName("jake");
    sheep.setLocalhost("北京");
    Sheep sheep1 = new Sheep();
    sheep1.setName("jake son");
    sheep.setSon(sheep1);
    Sheep clone1 = (Sheep) sheep.clone();
    Sheep clone2 = (Sheep) sheep.clone();
    clone2.getSon().setName("我不是 son 了");
    Sheep clone3 = (Sheep) sheep.clone();
    Sheep clone4 = (Sheep) sheep.clone();
    System.out.println("clone1 = " + clone1+"--son hashCode:"+clone1.getSon().hashCode());
    System.out.println("clone2 = " + clone2+"--son hashCode:"+clone2.getSon().hashCode());
    System.out.println("clone3 = " + clone3+"--son hashCode:"+clone3.getSon().hashCode());
    System.out.println("clone4 = " + clone4+"--son hashCode:"+clone4.getSon().hashCode());
}

输出结果:可以看到所有克隆对象的son属性都发生了变化

clone1 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234
clone2 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234
clone3 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234
clone4 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234

深拷贝

  • 复制对象的所有基本数据类型的成员变量值
  • 为所有引用数据类型的成员变量申请存储空间,并复制每一个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象而不是 仅限于 基本数据类型

实现方式

  • 重写clone的方法来实现深拷贝
  • 通过序列化实现深拷贝

重写clone
这里就只贴一下clone 方法

@Override
protected Object clone() throws CloneNotSupportedException {
    Object sheep=null;
    //首先拷贝基础数据类型
    sheep= super.clone();
    Sheep sheep1= (Sheep) sheep;
    //拷贝引用对象数据类型类型
    if(!ObjectUtils.isEmpty(sheep1.son)){
        //因为我们的引用对象还是本类所以这里需要做一下null的处理
        sheep1.son = (Sheep) son.clone();
    }
    return sheep;
}

进行测试:

public class Test001 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep();
        sheep.setName("jake");
        sheep.setLocalhost("北京");
        Sheep sheep1 = new Sheep();
        sheep1.setName("jake son");
        sheep.setSon(sheep1);
        Sheep clone1 = (Sheep) sheep.clone();
        clone1.getSon().setName("我不是 son 了");
        System.out.println("clone1 = " + clone1+"--son hashCode:"+clone1.getSon().hashCode());
        System.out.println("sheep = " + sheep+"--son hashCode:"+sheep.getSon().hashCode());

    }
}

输出结果:

sheep = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258
clone1 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234

序列化 推荐使用

public Object cloneSheep(){

    ByteArrayOutputStream byteArrayOutputStream=null;
    ObjectOutputStream objectOutputStream=null;
    ByteArrayInputStream byteArrayInputStream=null;
    ObjectInputStream objectInputStream=null;
       try {
           //序列化
           byteArrayOutputStream=new ByteArrayOutputStream();
           objectOutputStream= new ObjectOutputStream(byteArrayOutputStream);
           objectOutputStream.writeObject(this);//将当前对象以流的形式进行输出
           //反序列化
           byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
           objectInputStream=new ObjectInputStream(byteArrayInputStream);
           Sheep sheep= (Sheep) objectInputStream.readObject();
           return sheep;
       }catch (Exception e){
           System.out.println("e = " + e);
       }finally {
           try {
               byteArrayOutputStream.close();
               objectOutputStream.close();
               byteArrayInputStream.close();
               objectInputStream.close();
           }catch (Exception e){
               System.out.println("关闭流异常 = " + e);
           }
       }

       return null;
}

测试输出结果: clone1 = Sheep(name=jake, localhost=北京, son=Sheep(name=我不是 son 了, localhost=null, son=null))--son hashCode:1949092234 sheep = Sheep(name=jake, localhost=北京, son=Sheep(name=jake son, localhost=null, son=null))--son hashCode:124076258

两种方法的总结:

  • 重写clone 的方法 在里面要去处理每一个引用对象去单独clone,不太友好。
  • 使用序列化的方法 是将整个对象作为一个整体进行深拷贝,对多个引用对象比较友好 推荐使用

总结:原型模式

  • 创建新的对象比较复杂是,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
  • 不重新初始化对象,而是动态的获取运行时的状态。且更易于扩展,对象属性的增加和减少并不会影响 克隆。
  • 缺点也比较明显,需要每个类都要有一个克隆方法,对于老类来说需要修改其源码不太友好。新类可以直接添加

实践是检验真理的唯一方法! 明天见🥰🥰🥰