前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >漫谈原型模式

漫谈原型模式

作者头像
WindCoder
发布2020-05-06 15:09:33
3810
发布2020-05-06 15:09:33
举报
文章被收录于专栏:WindCoderWindCoder

1. 什么是

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

1.1 对象的创建成本比较大的场景

创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。

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

1.2 最快速地clone一个HashMap散列表

以如何最快速地clone一个HashMap散列表为例。

当需要将数据库存储的搜索关键字信息存入内存以备后续需求调用。可以直接使用Java语言中提供的 HashMap 容器来实现。其中,HashMap 的 key 为搜索关键词,value 为关键词详细信息(比如搜索次数)。我们只需要将数据从数据库中读取出来,放入 HashMap 就可以了。

当另一个系统B同时需要操作该关键字信息数据时,为了保证系统 A 中数据的实时性,只需要在系统 A 中,记录当前数据的版本 Va 对应的更新时间 Ta,从数据库中捞出更新时间大于 Ta 的所有搜索关键词,也就是找出 Va 版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。存在则更新,不存在就插入。

当存在更新的需求,如1、 任何时刻,系统 A 中的所有数据都必须是同一个版本的;2、同时在更新内存数据的时候,系统 A 不能处于不可用状态,也就是不能停机更新数据时,解决方案如下:把正在使用的数据的版本定义为“服务版本”,当要更新内存中的数据的时候,并不直接在服务版本(假设是版本 a 数据)上更新,而是重新创建另一个版本数据(假设是版本 b 数据),等新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b。

但这样做新版本(newKeywords )的构建的成本比较高:从数据库中读出,然后计算哈希值,构建 newKeywords,过程会比较耗时。

此时原型模式便可解决该问题。

2. 实现

原型模式基于拷贝已有对象的数据(深拷贝和浅拷贝)实现。

浅拷贝和深拷贝的区别在于:

  • 浅拷贝只会复制索引(散列表),不会复制数据(SearchWord 对象)本身
  • 深拷贝不仅仅会复制索引,还会复制数据本身。故深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。

2.1 浅拷贝实现

在 Java 语言中,Object 类的 clone() 方法执行的就是我们刚刚说的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象(SearchWord)的内存地址,不会递归地拷贝引用对象本身。

  • 如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的
  • 对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了
代码语言:txt
复制
/**
 * 原型模式--浅拷贝
 *
 *  利用Java 中的 clone() 语法来复制一个对象。
 *
 * 最耗时的还是从数据库中取数据的操作。相对于数据库的 IO 操作来说,内存操作和 CPU 计算的耗时都是可以忽略的。
 *
 * 此时处于浅拷贝,当我们通过 newKeywords 更新 SearchWord 对象的时候,newKeywords 和 currentKeywords 因为指向相同的一组 SearchWord 对象,
 * 就会导致 currentKeywords 中指向的 SearchWord,有的是老版本的,有的是新版本的
 * 从而无法满足:currentKeywords 中的数据在任何时刻都是同一个版本的,不存在介于老版本与新版本之间的中间状态
 *
 * 解决方案是改用深拷贝实现。
 */
public class PrototypeDemo3 {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<String, SearchWord>();
    private long lastUpateTime = -1;

    public void refresh() {
        // 原型模式 拷贝已有对象的数据,更新少量差值
        HashMap<String, SearchWord> newKeyWords = (HashMap<String, SearchWord>) currentKeywords.clone();

        // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpateTime);
        long maxNewUpdateTime = lastUpateTime;

        for (SearchWord searchWord: toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdateTime) {
                maxNewUpdateTime = 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);
            }

        }
        lastUpateTime = maxNewUpdateTime;
        currentKeywords = newKeyWords;

    }

    private List<SearchWord> getSearchWords(long lastUpateTime) {
        // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
         return null;
    }
}

2.2 深拷贝实现

深拷贝实现有两种方案:递归拷贝和对象序列化

2.2.1 递归拷贝

第一种方法:递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。根据这个思路对之前的代码进行重构。

代码语言:txt
复制
/**
 * 原型模式--深拷贝-递归拷贝对象
 *
 * 递归拷贝对象、对象的引用对象以及引用对象的引用对象……
 * 直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。
 * 根据这个思路对之前的代码进行重构
 */
public class PrototypeDemo4 {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<String, SearchWord>();
    private long lastUpateTime = -1;

    public void refresh() {
        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的数据,放入到currentKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpateTime);
        long maxNewUpdateTime = lastUpateTime;

        for (SearchWord searchWord: toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdateTime) {
                maxNewUpdateTime = 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);
            }

        }
        lastUpateTime = maxNewUpdateTime;
        currentKeywords = newKeyWords;

    }

    private List<SearchWord> getSearchWords(long lastUpateTime) {
        // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
         return null;
    }
}
2.2.2 对象序列化与反序列化

第二种方法:先将对象序列化,然后再反序列化成新的对象

代码语言:txt
复制
/**
 * 原型模式--深拷贝-对象序列化
 *
 * 仅用于提供思路参考,不保证代码本身正确性以及正常运行。
 */
public class PrototypeDemo5 {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<String, SearchWord>();
    private long lastUpateTime = -1;

    public void refresh() {
        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的数据,放入到currentKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpateTime);
        long maxNewUpdateTime = lastUpateTime;

        for (SearchWord searchWord: toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdateTime) {
                maxNewUpdateTime = searchWord.getLastUpdateTime();
            }
            if (newKeyWords.containsKey(searchWord.getKeyword())) {
                SearchWord oldSearchWOrd = null;
                try {
                    oldSearchWOrd = (SearchWord) deepCopy(newKeyWords.get(searchWord.getKeyword()));
                    oldSearchWOrd.setCount(searchWord.getCount());
                    oldSearchWOrd.setLastUpdateTime(searchWord.getLastUpdateTime());
                    newKeyWords.replace(oldSearchWOrd.getKeyword(), oldSearchWOrd);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                newKeyWords.put(searchWord.getKeyword(), searchWord);
            }

        }
        lastUpateTime = maxNewUpdateTime;
        currentKeywords = newKeyWords;

    }

    /**
     * 先将对象序列化,然后再反序列化成新的对象
     * @param object
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deepCopy(Object object) throws IOException, ClassNotFoundException {
        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();
    }

    private List<SearchWord> getSearchWords(long lastUpateTime) {
        // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
         return null;
    }
}

2.3 深浅拷贝结合

无论单纯使用深拷贝的哪种实现方式,深拷贝都要比浅拷贝耗时、耗内存空间。

为解决该问题,可以先采用浅拷贝的方式创建 newKeywords。对于需要更新的 SearchWord 对象,我们再使用深度拷贝的方式创建一份新的对象,替换 newKeywords 中的老对象

需要更新的数据是很少的。这种方式即利用了浅拷贝节省时间、空间的优点,又能保证 currentKeywords 中的中数据都是老版本的数据。

代码语言:txt
复制
/**
 *  原型模式--深浅拷贝结合
 */
public class PrototypeDemo6 {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<String, SearchWord>();
    private long lastUpateTime = -1;

    public void refresh() {
        // 浅拷贝获取所有对象
        HashMap<String, SearchWord> newKeyWords = (HashMap<String, SearchWord>) currentKeywords.clone();


        // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeyWords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpateTime);
        long maxNewUpdateTime = lastUpateTime;

        for (SearchWord searchWord: toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdateTime) {
                maxNewUpdateTime = searchWord.getLastUpdateTime();
            }
            if (newKeyWords.containsKey(searchWord.getKeyword())) {
                newKeyWords.remove(searchWord.getKeyword());
            }
            newKeyWords.put(searchWord.getKeyword(), searchWord);

        }
        lastUpateTime = maxNewUpdateTime;
        currentKeywords = newKeyWords;

    }

    private List<SearchWord> getSearchWords(long lastUpateTime) {
        // TODO: 从数据库中取出更新时间>lastUpdateTime的数据
         return null;
    }
}

3. 优缺点

该部分来自《Head First设计模式》,有的地方可能过于抽象或官方语言,仅作相关参考。

3.1 优点

  • 向客户隐藏制造新实例的复杂性。
  • 提供让客户能够产生未知类型对象的选项。
  • 在某些环境下,复制对象比创建对象更有效。

3.2 用途和缺点

  • 在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时(即,当创建给定类的实例的过程很昂贵或者很复杂时),可以考虑原型模式。
  • 使用原型模式的缺点:对象的复制有时相当复杂。

参考资料

相关下载

点击下载

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 什么是
    • 1.1 对象的创建成本比较大的场景
      • 1.2 最快速地clone一个HashMap散列表
      • 2. 实现
        • 2.1 浅拷贝实现
          • 2.2 深拷贝实现
            • 2.2.1 递归拷贝
            • 2.2.2 对象序列化与反序列化
          • 2.3 深浅拷贝结合
          • 3. 优缺点
            • 3.1 优点
              • 3.2 用途和缺点
              • 参考资料
              • 相关下载
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档