【设计模式系列】创建型之原型模式ProtoType Design Pattern(不常用)

233 阅读5分钟

人生很短,只有几百个月;人生很长,大约有31亿5千3百6十万秒,在保证身体的前提下,多学点东西,驱散自己的无知。。。

题外话:刚看了一个讲阿里的十面的博客,有点儿自闭。。。吐槽下心情,不然今晚失眠了。。。

科学而完美的解释

首先,你需要知道,这是一个很不常用的设计模式,其次,它科学而完美的解释如下:如果一个对象的创建成本比较大、比较耗时或者同一类的对象间只有几个属性值不一样,在这种情况下,我们都会基于已有对象(原型)复制(拷贝)出一个新对象,以达到节省创建时间的目的,这就是原型模式。

使用场景分析

对于一般的业务系统,创建一个类涉及的内存申请,变量赋值所需要的时间可以忽略不计,这时候引入一个复杂的原型模式就得不偿失了。当创建一个对象时需要从RPC,数据库,网络,文件系统等非常慢的IO中读取数据时,就需考虑下创建需要的时间和成本。这时候就可以使用原型模式进行性能优化。

案例分析

需求:假设系统中需要维护一张关键词相关信息的数据库表,同时这张表的信息需要加载到系统内存中,以哈希表的形式存在;然后数据库表中的关键词信息(关键词名称,搜索次数,更新时间,当前关键词版本等)会被其他子系统定时更新,同时系统内存中的hash表也需要更新(不一定非要实时,但是需要能自动更新)。数据库表的表格如下:

数据库表
当关键词的次数更新了,当前关键词的version版本就会加1。当定时更新系统中hash表时,不需要把整张hash表的数据都重新从数据库中读取,而是将上次更新到本次更新时间段内更新的数据重新加载到hash表中就行了,这样就极大减少了从数据库中读取的数据量。这里其实我个人感觉是很没必要使用原型模式的,因为有很多方法可以实现这个需求。

代码实现思路分析: 记录下本次更新的时间TimeA,到下次更新系统hash表时,查询出数据库表中更新时间戳大于TimeA的数据,根据关键词名,筛选出hash表中需要更新的,将其更新,没有的就直接插入到hash表中。

使用原型模式:使用原型模式较之上面的实现思路就多了一不,将老hash表先拷贝一份做成新hash表,然后所有的更新和插入操作都基于新hash表做。这里使用原型模式我唯一能想到用途就是可以对比两个版本关键词的差异,而且这种还必须使用深拷贝才能实现。

下面分析下原型模式有两种实现方式:浅拷贝和深拷贝

浅拷贝:拷贝的只是内存地址,两个map中value中存储的都是对象的内存地址,指向的都是相同的内存对象。

浅拷贝

深拷贝:拷贝的是内存地址指向的对象的内容,在内存中重新生成一个内容一样但内存地址不一样的对象,将其内存地址赋值到map中的value中。

深拷贝

原型模式(深拷贝、浅拷贝)代码实现

/**
 * @author a small asshole
 * @version 1.0
 * @description 关键词信息类
 * @date in 11:05 2020/5/26
 * @since 1.0
 */
public class KeyWord {
    private String keyWordName;
    private Long updateTime;
    private Long searchTime;

    public String getKeyWordName() {
        return keyWordName;
    }

    public void setKeyWordName(String keyWordName) {
        this.keyWordName = keyWordName;
    }

    public Long getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Long updateTime) {
        this.updateTime = updateTime;
    }

    public Long getSearchTime() {
        return searchTime;
    }

    public void setSearchTime(Long searchTime) {
        this.searchTime = searchTime;
    }
}

/**
 * @author a small asshole
 * @version 1.0
 * @description 关键词管理类----原型模式
 * @date in 11:07 2020/5/26
 * @since 1.0
 */
public class KeyWordsManager {
    /**
     * 关键词hash
     */
    private HashMap<String, KeyWord> concurrentMap = new HashMap<>();
    /**
     * 上次更新时间
     */
    private Long lastUpdateTime = -1L;

    /**
     * 浅拷贝
     */
    public void refreshMapShallow() {
        List<KeyWord> keyWordsInDataBase = getKeyWordsInDataBase(lastUpdateTime);
        if (!CollectionUtils.isEmpty(keyWordsInDataBase)) {
            int initialCapacity = (int) ((keyWordsInDataBase.size() + concurrentMap.size()) / .75) + 1;
            HashMap<String, KeyWord> newHashMap = new HashMap<>(initialCapacity);
            //1.使用HashMap中的clone方法
            //newHashMap = (HashMap<String, KeyWord>)concurrentMap.clone();
            //2. 使用putAll方法,效果都一样
            newHashMap.putAll(concurrentMap);
            Long newMaxUpdateTime = lastUpdateTime;
            for (KeyWord keyWord : keyWordsInDataBase) {
                if (keyWord.getUpdateTime() > newMaxUpdateTime) {
                    newMaxUpdateTime = keyWord.getUpdateTime();
                }
                String keyWordName = keyWord.getKeyWordName();
                KeyWord oldKeyWord = newHashMap.get(keyWordName);
                if (oldKeyWord != null){
                    oldKeyWord.setKeyWordName(keyWord.getKeyWordName());
                    oldKeyWord.setSearchTime(keyWord.getSearchTime());
                    oldKeyWord.setUpdateTime(keyWord.getUpdateTime());
                }else {
                    newHashMap.put(keyWordName, keyWord);
                }
                concurrentMap = newHashMap;
                lastUpdateTime = newMaxUpdateTime;
            }
        }
    }

    /**
     * 深拷贝
     */
    public void refreshMapDeep() {
        List<KeyWord> keyWordsInDataBase = getKeyWordsInDataBase(lastUpdateTime);
        if (!CollectionUtils.isEmpty(keyWordsInDataBase)) {
            int initialCapacity = (int) ((keyWordsInDataBase.size() + concurrentMap.size()) / .75) + 1;
            HashMap<String, KeyWord> newHashMap = new HashMap<>(initialCapacity);
            //1.使用HashMap中的clone方法
            //newHashMap = (HashMap<String, KeyWord>)concurrentMap.clone();
            //2. 使用putAll方法,效果都一样
            newHashMap.putAll(concurrentMap);
            Long newMaxUpdateTime = lastUpdateTime;
            for (KeyWord keyWord : keyWordsInDataBase) {
                if (keyWord.getUpdateTime() > newMaxUpdateTime) {
                    newMaxUpdateTime = keyWord.getUpdateTime();
                }
                String keyWordName = keyWord.getKeyWordName();
                //remove方法如果对应的key不存在,remove返回null,不会报错
                newHashMap.remove(keyWordName);
                newHashMap.put(keyWordName,keyWord);
                concurrentMap = newHashMap;
                lastUpdateTime = newMaxUpdateTime;
            }
        }
    }

    /**
     * 查询数据库获取大于{@code time}的关键词信息
     *
     * @param time
     * @return
     */
    private List<KeyWord> getKeyWordsInDataBase(Long time) {
        //TODO 查询数据库,这里就不实现了
        return null;
    }
}

/**
 * @author a small asshole
 * @version 1.0
 * @description 原型模式测试类
 * @date in 14:14 2020/5/26
 * @since 1.0
 */
public class ProtoTypeTest {
    public static void main(String[] args) {
        KeyWordsManager keyWordsManager = new KeyWordsManager();
        //浅拷贝
        keyWordsManager.refreshMapShallow();
        //深拷贝
        keyWordsManager.refreshMapDeep();
    }
}

说实话,原型模式用的真的不多,我到目前为之都没接触到过,可能是工作年纪短吧,在我看来,原型模式的实现可替代性太强,有很多方法都可以做到这样的功能。只是做做了解就可以了,不过深拷贝,浅拷贝的区别还是需要知道的。。。。。

noted:我这只是类似于读书笔记的博客,详细的设计模式讲解,我还是推荐极客时间上的《设计模式》专栏,很详细,详细到我都感觉冗余了(极限夸张的表扬手法)