4.java设计模式之原型模式

基本需求:

  • 有一个Sheep类的对象,我们现在需要创建100个和该对象属性完全一致的对象

传统方式:

  • 使用new关键字创建100个对象,将这一百个对象属性使用原型的get方法进行复制

  • 代码实现

    • // Sheep类
      @Data
      @ToString
      @NoArgsConstructor
      @AllArgsConstructor
      public class Sheep {
      
         private String name;
      
         private int age;
      
      }
      
      // Client
      public static void main(String[] args) {
         Sheep sheep = new Sheep("zhangsan",20);
         // 传统方式
         Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge());
         // ......
         System.out.println(sheep1);
      }
      
  • 缺陷及改进

    • 容易理解好操作,使用时需要频繁获取原型对象的属性,如果原型对象复杂,则效率很低,总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
    • 使用java基类中Object的clone()方法,可以复制对象,需要类实现Cloneable接口,该接口表示该类有复制能力,也就是原型模式

基本介绍:

  • 创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用

  • 实现方式:实现Cloneable接口,复写clone()方法或通过序列化,原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口

  • 优缺点:

    • 性能提高,逃避构造函数的约束
    • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候
    • 必须实现 Cloneable 接口
  • 使用场景:

    • 资源优化场景、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
    • 性能和安全要求的场景、通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
    • 一个对象多个修改者的场景、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
    • 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者
  • UMl类图

  • 代码实现

    • @Data
      @ToString
      @NoArgsConstructor
      @AllArgsConstructor
      public class Sheep implements Serializable,Cloneable {
      
         private String name;
      
         private int age;
      
         // 默认的super.clone()实现的浅拷贝,即对于基本类型和字符串拷贝直接赋值,对于引用类型的直接拷贝的引用(并没有重新创建该内部的对象)
         private Friend friend;
      
         @Override
         protected Object clone() throws CloneNotSupportedException {
             // 如果没有实现Cloneable接口 调用该方法会抛出CloneNotSupportedException异常
             return super.clone();
         }
      
      }
      
      public class Client {
         public static void main(String[] args) throws CloneNotSupportedException {
             Friend friend = new Friend("666");
             Sheep sheep = new Sheep("zhangsan",23,friend);
             // 使用默认clone方法 采用的是浅拷贝
             Sheep sheep1 = (Sheep) sheep.clone();
             Sheep sheep2 = (Sheep) sheep.clone();
             System.out.println(sheep1.toString());
             System.out.println(sheep2.toString());
             // 结果是true 说明clone出来的sheep1和sheep2中引用的是同一Friend对象,并没有将Friend对象再克隆一份,是浅拷贝
             // 深拷贝则是将Friend对象再克隆一份,sheep1和sheep2中引用的不是同一Friend对象 结果为false
             System.out.println(sheep1.getFriend() == sheep2.getFriend());
         }
      }
      

spring源码:

  • 在spring中AbstractBeanFactory类中doGetBean()方法中有使用到原型模式,配置bean标签时,有个属性为Scope

    • if (mbd.isSingleton()) {
         sharedInstance = this.getSingleton(beanName,() -> {
             try {
                 return this.createBean(beanName,mbd,args);
             } catch (BeansException var5) {
                 this.destroySingleton(beanName);
                 throw var5;
             }
         });
         bean = this.getObjectForBeanInstance(sharedInstance,name,beanName,mbd);
         // 此处判断了是否使用了prototype
      } else if (mbd.isPrototype()) {
         var11 = null;
      
         Object prototypeInstance;
         try {
             this.beforePrototypeCreation(beanName);
             prototypeInstance = this.createBean(beanName,args);
         } finally {
             this.afterPrototypeCreation(beanName);
         }
      }
      

浅拷贝和深拷贝:

  • 默认的super.clone()实现的浅拷贝,即对于基本类型和字符串拷贝直接赋值,对于引用类型的直接拷贝的引用(并没有重新创建该内部的对象)

  • 深拷贝则是将对象内部引用类型的属性会创建新的对象,使用的不是同一个对象

  • 深拷贝实现方式

    • 重写clone()方法,需要实现Cloneable接口
    • 使用序列化,需要实Serializable
  • 代码实现

  • // Sheep 类 重写clone方法 和 增加deepClone方法
    @Data
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class Sheep implements Serializable,Cloneable {
    
       private String name;
    
       private int age;
    
       // 默认的super.clone()实现的浅拷贝,即对于基本类型和字符串拷贝直接赋值,对于引用类型的直接拷贝的引用(并没有重新创建该内部的对象)
       private Friend friend;
    
       @Override
       protected Object clone() throws CloneNotSupportedException {
           // 如果没有实现Cloneable接口 调用该方法会抛出CloneNotSupportedException异常
           // return super.clone();
    
           // 实现深拷贝方式一 重写clone方法 此种方法不好用
           // 如果最外层对象有多个引用类型的属性,则每个引用属性都要进行克隆,很麻烦
           // 如果层对象内部引用类型的属性仍然有引用类型的属性,则需要多层克隆,不易实现
           // 1.先浅拷贝最外层的对象
           Sheep sheep = (Sheep) super.clone();
           // 2.再拷贝外层对象内部引用类型的属性
           if (null != sheep.getFriend()) {
               sheep.setFriend((Friend) sheep.getFriend().clone());
           }
           return sheep;
       }
    
       public Object deepClone() {
           // 实现深拷贝方式二 序列化方式
           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);
               // 从字节数组流中读取对象并返回
               return (Sheep) objectInputStream.readObject();
           } catch (Exception e) {
               e.printStackTrace();
               return null;
           } finally {
               try {
                   objectInputStream.close();
                   byteArrayInputStream.close();
                   objectOutputStream.close();
                   byteArrayOutputStream.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
    
    }
    

注意事项:

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 不用重新初始化对象,而是动态地获得对象运行时的状态
  • 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  • 在实现深克隆的时候可能需要比较复杂的代码
  • 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结