设计模式-原形模式

发布于 — 2019 年 10 月 14 日
#design

原型模式的原理与应用

如果对象的创建成本比较大, 而同一个类的不同对象之间差别不大(大部分字段都相同), 在这种情况下, 我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象, 以达到节省创建时间的目的, 这种基于原型来创建对象的方式就叫做原型设计模式.

何为"对象的创建成本比较大"

创建对象包含的申请内存, 变量赋值这一过程, 本身并不会花费太多时间, 对于大部分业务系统来说, 这点时间是可以忽略的.

如果对象中的数据需要复杂的计算才能得到(比如排序, 计算哈希值), 或者需要从RPC, 网络, 数据库, 文件系统等非常慢速的IO中读取, 这种情况下, 我们就可以利用原型模式, 从其他已有对象中直接拷贝得到, 而不用每次在创建新对象时, 都重复执行这些耗时的操作.

原型模式的实现方式: 深拷贝与浅拷贝

深拷贝与浅拷贝的区别在于: 浅拷贝只会复制对象的索引(内存地址), 而不会复制对象本身. 深拷贝不仅会复制索引还好复制对象本身.

浅拷贝得到的对象跟原始对象共享数据, 当值进行修改后, 两边的值都会被修改, 因为他们是一个值.

而深拷贝得到的是一份完完全全独立的对象.

Java中的clone方法执行的就是浅拷贝.

进行深拷贝有两种方法:

  1. 递归拷贝对象, 对象的引用对象以及饮用对象的引用对象, 直到要拷贝的对象只包含基本数据类型, 没有饮用对象为止.

     public class Demo {
       private HashMap<String, SearchWord> currentKeywords=new HashMap<>();
       private long lastUpdateTime = -1;
     
       public void refresh() {
         // Deep copy
         HashMap<String, SearchWord> newKeywords = new HashMap<>();
         for (HashMap.Entry<String, SearchWord> e : currentKeywords.entrySet()) {
           SearchWord searchWord = e.getValue();
           SearchWord newSearchWord = new SearchWord(
                   searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastUpdateTime());
           newKeywords.put(e.getKey(), newSearchWord);
         }
     
         // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
         List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
         long maxNewUpdatedTime = lastUpdateTime;
         for (SearchWord searchWord : toBeUpdatedSearchWords) {
           if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
             maxNewUpdatedTime = searchWord.getLastUpdateTime();
           }
           if (newKeywords.containsKey(searchWord.getKeyword())) {
             SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
             oldSearchWord.setCount(searchWord.getCount());
             oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
           } else {
             newKeywords.put(searchWord.getKeyword(), searchWord);
           }
         }
     
         lastUpdateTime = maxNewUpdatedTime;
         currentKeywords = newKeywords;
       }
     
       private List<SearchWord> getSearchWords(long lastUpdateTime) {
         // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
         return null;
       }
     
     }
    
  2. 先将对象序列化, 然后再反序列化成新的对象

     public Object deepCopy(Object object) {
       ByteArrayOutputStream bo = new ByteArrayOutputStream();
       ObjectOutputStream oo = new ObjectOutputStream(bo);
       oo.writeObject(object);
    
       ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
       ObjectInputStream oi = new ObjectInputStream(bi);
    
       return oi.readObject();
     }
    

    优化

    这两种方法, 不管采用哪种, 深拷贝都要比浅拷贝耗时, 耗内存空间. 我们可以先采用浅拷贝的方法来进行复制, 对于需要更新的对象, 再使用深拷贝的方式来创建一份新的对象, 替换老对象. 这种方法即利用了浅拷贝节省时间, 空间的优点. 又能保证数据符合要求.