【设计模式】原型设计模式

74 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情

概念

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

使用原型模式的步骤

  1. 目标类实现Cloneable接口 (标志接口)
  2. 重写接口的clone方法,将访问修饰符修改为public。

特定场景代码演示

  • 问题

    • OA系统中,有一个员工提交周报的功能,每个周五,总结本周工作内容,写出下周工作计划。
  • 反例

    • 代码
     public class WeekReport {
     ​
         //自增主键记录
         private static int incrementId = 0;
     ​
         //唯一标识
         private int id;
         //部门
         private String emp;
         //总结
         private String summary;
         //下周计划
         private String plain;
         //意见
         private String suggestion;
         //提交时间
         private Date time;
     ​
         public WeekReport(String emp, String summary, String plain, String suggestion, Date time) {
     ​
             this.id = ++incrementId;
             this.emp = emp;
             this.summary = summary;
             this.plain = plain;
             this.suggestion = suggestion;
             this.time = time;
         }
     }
     public class Test {
     ​
         public static void main(String[] args) {
     ​
             //第一周周报
             WeekReport weekReport = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date());
     ​
             //第二周周报
             WeekReport weekReport2 = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date());
             
         }
     ​
     }
    
    • 分析

      大部分内容均相同,但是每周填写均需填写相同的部分。降低了工作效率。

  • 正例(原型模式)

    • 代码

       public class WeekReport implements Cloneable {
       ​
           //自增主键记录
           private static int incrementId = 0;
       ​
           //唯一标识
           private int id;
           //部门
           private String emp;
           //总结
           private String summary;
           //下周计划
           private String plain;
           //意见
           private String suggestion;
           //提交时间
           private Date time;
           
           @Override
           public Object clone() throws CloneNotSupportedException {
               return super.clone();
           }
       ​
           public WeekReport(String emp, String summary, String plain, String suggestion, Date time) {
       ​
               this.id = ++incrementId;
               this.emp = emp;
               this.summary = summary;
               this.plain = plain;
               this.suggestion = suggestion;
               this.time = time;
           }
       }
       public class Test {
       ​
           public static void main(String[] args) {
       ​
               //第一周周报
               WeekReport weekReport = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date());
       ​
               //第二周周报
               WeekReport weekReport2 = (WeekReport) weekReport1.clone();
               
           }
       ​
       }
      
    • 分析

      • 实现Cloneable接口 重写clone方法

      • 下一周写周报的时候,直接克隆(浅拷贝)上一周的周报实例,修改需要修改的部分即可。

      • 存在的问题(浅拷贝的共享引用对象问题)

        • 浅拷贝是直接复制二进制数据,其中的引用会也复制地址给副本,此时,源对象和克隆的对象是引用同一个对象。

        • 解决方案1

           @Override
               public WeekReport clone() throws CloneNotSupportedException {
                   //通过周报1克隆周报2
                   WeekReport clone = (WeekReport) super.clone();
                   //通过周报2 克隆 data(指向周报2中的日期对象)
                   Object data = clone.getTime().clone();
                   // 将data赋新值
                   data = new Date();
                   return clone;
               }
          
        • 解决方案2 使用序列化

            @Override
               public WeekReport clone() throws CloneNotSupportedException {
                   //将对象写入内存
                       ByteOutputStream byteOutputStream = new ByteOutputStream();
                       ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream);
                       objectOutputStream.writeObject(this);
           ​
                       //从内存读取数据
                       byte[] bytes = byteOutputStream.toByteArray();
                       //将字节数作为input
                       InputStream inputStream = new ByteArrayInputStream(bytes);
                       //读取数据
                       ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                       return (WeekReport)objectInputStream.readObject();
               }
          
  • 最终的解决方案(原型模式)

     @Override
         public WeekReport clone() throws CloneNotSupportedException {
             
             try {
                 //将对象写入内存
                 ByteOutputStream byteOutputStream = new ByteOutputStream();
                 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream);
                 objectOutputStream.writeObject(this);
     ​
                 //从内存读取数据到字节数组
                 byte[] bytes = byteOutputStream.toByteArray();
                 //将字节数组作为input
                 InputStream inputStream = new ByteArrayInputStream(bytes);
                 //读取数据
                 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                 return (WeekReport)objectInputStream.readObject();
     ​
             } catch (Exception e) {
                 e.printStackTrace();
             }
             return null;
         }
    

优缺点

原型模式的优点:

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。