guava的LoadingCache是怎么使用建造者模式的

328 阅读4分钟

随便聊聊

之前看设计模式的步骤是:第一章:单例模式 -> 第二章:工厂模式 ->好像没啥神奇的,不看了。。 ;过段时间, 单例模式 -> 第二章:工厂模式 -> 算了,不看了。。;要面试了,硬着头皮看完一遍,第二天,我看的是啥??? 这种低效且重复的学习方式不应该再发生了。我总结了一下,将设计模式的书很多,风格也各式各样,但对我来书,都是入门,只是去了解这种模式是干啥的,看完就忘(就算按书上的例子自己模仿一遍)。所以我就认为设计模式不是初学者可以掌握的,所以我不学了本片完 。 。 。 开玩笑,不学怎么变成高手呢?怎么装杯呢? 根据之前学习的经验,我发现记不住的原因就是自己的理解不深刻,怎么可以理解深刻呢?除了天赋异禀,就是勤学多练,ok,怎么练呢?整天crud,各种框架把这些设计模式整的明明白白的,就是做填空题,哪有机会练呢?于是,我想,既然框架中用,那我就看看他们怎么用的,这样跟别人讲的时候也不会干巴巴的,还能无形之间告诉别人自己看了很多源码。 zh

正文

建造者模式还是很常见的,在google提供的guava工具包中,有LoadingCache这么一个接口,可以用作本地缓存,构造这个接口的实现:

    LoadingCache<String, Object> cache
            = CacheBuilder.newBuilder()
            // 设置并发级别为8,并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(500).refreshAfterWrite(1, TimeUnit.NANOSECONDS)
            .concurrencyLevel(500).refreshAfterWrite(2, TimeUnit.SECONDS)
            // 设置写缓存后x秒钟过期
            .expireAfterWrite(1L, TimeUnit.MINUTES)
            // 设置缓存容器的初始容量
            .initialCapacity(100)
            // 设置缓存最大容量为500,超过500之后就会按照LR0移除缓存项
            .maximumSize(500)
            // 设置要统计缓存的命中率
            .recordStats()
            // 设置缓存的移除通知
            .removalListener((notification)->{
                System.out.println("remove "+ notification.getKey());
            })
            // build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
            .build(new CacheLoader<String, Object>() {
                @Override
                public Object load(String key) throws Exception {
                    //System.out.println("load Object " + key);
                    return null;
                }
            });

可以看到,建造这模式适用于,构造参数多且复杂的场景。 可以看到,建造这模式适用于,构造参数多且复杂的场景。

那么根据建造者模式的方法论,我们去找一下建造LoadingCache所需要的角色分别对应了那些类

建造者(Builder)模式的主要角色如下。 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

  • 所谓的Product当然是我们需要的对象LoadingCache啦,因为它是接口,所以我们实际建造的是它的其中一个实现类,这里我们建造Product的是com.google.common.cache.LocalCache.LocalLoadingCache
  • 抽象建造者(Builder)这里就是com.google.common.cache.CacheBuilder,这里的getResult()就是该类的com.google.common.cache.CacheBuilder#build(com.google.common.cache.CacheLoader<? super K1,V1>)方法
  • 具体建造者(Concrete Builder)在这里只有一个建造者,所以没有体现,但是我们可以明白,当一个产品的各个组成完全不同的话,可以使用多个建造者,每个建造者建造不同的产品。
  • 指挥者,该角色来指挥建造的过程,比如盖房子要先打地基,再砌墙,最后加房顶。这里使用链式编程,没有使用该角色。

然后,来看一下建造removalListener的源码

com.google.common.cache.CacheBuilder#removalListener

    @CheckReturnValue
    public <K1 extends K, V1 extends V> CacheBuilder<K1, V1> removalListener(RemovalListener<? super K1, ? super V1> listener) {
        Preconditions.checkState(this.removalListener == null);
        this.removalListener = (RemovalListener)Preconditions.checkNotNull(listener);
        return this;
    }

可以看出,复杂的建造过程由builder负责,我们创建对象是就不用关注了,只用知道需要组装那些组件即可。这也是我们使用建造者模式的原因,否则,每次创建对象都要写一大串复杂的代码。

总结

我并没有去过多的讲建造者模式的方法论,因为这些已经有太多的文章写了。我只是根据这些方法论去源码中印证,加深印象和理解,如果你了解过建造者模式,就不难看出,实际的建造者模式和设计模式的方法论中的还是有很大区别的,设计模式不是生搬硬套,要灵活应用。