JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

建造者(Builder)设计模式,对我来说,就像一个熟悉的陌生人:她是那样的熟悉,亲切,友善,她曼妙修长的身姿(链式调用),出现在了JDK、Spring框架、Mybatis框架、Hibernate框架等数不清的一流框架和类库里面。然而,当我想走近她,亲近她,了解她时,才发现她就像一个在外漂泊多年,离家已经很远的少女,早已不是她本初的模样。

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

Google首席Java架构师

GOF,四位设计模式前辈,创造了她。然而,她在java语言的发展中,受到JDK大佬级人物

Joshua Bloch(Java集合框架创始人,Google的首席Java架构师)的宠爱,在其《Effective java》中的大力推广和演绎,让她在java语言领域,特别是框架和类库开发领域,备受青睐,光彩照人。同时,她自身在发展过程中,女大十八变,由孤芳自赏,遥不可及的高冷范,蜕变为魅力四射,担当主角的御姐范。在两者之间,我思之再三,最后毅然决然的选择了后者,为了她,我宁愿选择,暂时的背离GOF 四位老前辈。

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

大家好,欢迎关注极客架构师,极客架构师,专注架构师成长,我是码农老吴。

本期是《架构师基本功之设计模式》的第10期,在上期,我一口气,分享了工厂系列模式里面的三个模式,简单工厂模式,工厂方法模式,抽象工厂模式。在本期,我将分享创建型设计模式里面的最后一个设计模式,建造者(builder)设计模式,也有翻译为生成器设计模式,创建者设计模式。

基本思路

框架和类库架构师的烦恼

假如你是guava类库的架构师,你该如何做

方案1:超级大的构造函数(简单粗暴)

方案2:重叠式构造函数(稍稍人性化)

方案3:javaBean方式(自助餐模式)

方案4:Joshua Bloch改进的建造者模式(主流方式)

方案5:Google架构师的方案(源码解析)

建造者(Builder Pattern)模式定义

建造者模式通用类图和代码

原始建造者模式的奇怪之处

框架和类库架构师的烦恼

对于普通的程序员,特别是java程序员,使用框架或者类库是家常便饭。而使用框架或者类库里面的组件之前,一个常见的工作,就是配置一堆参数。一个参数,几个参数,几十个参数,都有可能。了解每个参数的作用,了解如何配置合适的参数,是java程序员的看家本领。

而换个角度,对于框架或者类库的架构师(大多数有野心的程序员,都想开发一个自己的框架),则需要考虑,如何让框架或者类库的使用者,便捷,高效,安全的配置参数。这也是创建者(Builder)设计模式,人见人怜,大行其道的一个重要原因。

假如你是guava类库的架构师,你该如何做

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

上图是构建guava 的cache组件,可以配置的参数。

以google公司的guava类库cache组件为例,从上面的截图,可以看出来,CacheBuilder里面,有15个左右的参数,我们在使用cache组件的时候,一般不需要全部配置,可以根据需要,配置合适的几个参数即可。

假如你是这个类库的架构师,你该如何设计这个组件,让用户可以便捷,高效,安全地配置所需的参数呢。而且组件还可以轻松地对用户配置的参数,进行参数校验。同时,在多线程环境下,还需要保证组件的线程安全。我想对于一个即使没有多少经验的java程序员,至少能想到以下两到三个方案。

为了让这个案例,更符合设计模式知识的讲解,我们从上面的十几个参数里面,挑选出几个简单的,大家熟悉的属性,进行案例展示,并且约定里面有两个参数是必须的,其他是可选的。

我们看代码。

方案1:超级大的构造函数(简单粗暴)

ICache接口:缓存组件的接口

package com.geekarchitect.patterns.builder.demo03;

/**
 * Cache接口
 * @author 极客架构师@吴念
 * @createTime 2022/7/4
 */
public interface ICache {
    void put(K key,V value);
    V get(K key);
}

CacheImplV1:第一版代码

注意两点

1,前面两个参数initialCapacity,maximumSize,是必须的,不能为空,所以定义为final,在构造函数中,必须进行初始化。

2,这个类的构造函数,是一个超级大的构造函数,包含了所有参数的初始化。

package com.geekarchitect.patterns.builder.demo03;

import java.util.Map;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/4
 */
public class CacheImplV1 implements ICache {
    /**
     * 初始化容量,必须
     */
    private final int initialCapacity;
    /**
     * 最大数量,必须
     */
    private final long maximumSize;
    private final Map cacheMap = null;
    /**
     * 并行等级。决定segment数量的参数
     */
    private int concurrencyLevel = -1;
    /**
     * 最大权重
     */
    private long maximumWeight = -1L;
    /**
     * 写操作后失效时间
     */
    private long expireAfterWriteNanos = -1L;
    /**
     * 访问操作后失效时间
     */
    private long expireAfterAccessNanos = -1L;

    public CacheImplV1(int initialCapacity, long maximumSize, 
    int concurrencyLevel, long maximumWeight, long expireAfterWriteNanos,
     long expireAfterAccessNanos) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
        this.concurrencyLevel = concurrencyLevel;
        this.maximumWeight = maximumWeight;
        this.expireAfterWriteNanos = expireAfterWriteNanos;
        this.expireAfterAccessNanos = expireAfterAccessNanos;
    }

    @Override
    public String toString() {
        return "CacheImplV1{" +
                "initialCapacity=" + initialCapacity +
                ", maximumSize=" + maximumSize +
                ", cacheMap=" + cacheMap +
                ", concurrencyLevel=" + concurrencyLevel +
                ", maximumWeight=" + maximumWeight +
                ", expireAfterWriteNanos=" + expireAfterWriteNanos +
                ", expireAfterAccessNanos=" + expireAfterAccessNanos +
                '}';
    }

    @Override
    public void put(K key, V value) {
    }

    @Override
    public V get(K key) {
        return null;
    }
}

TestCache:测试类

package com.geekarchitect.patterns.builder.demo03;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/6
 */
public class TestCache {
    private static final Logger LOG = LoggerFactory.getLogger(TestCache.class);

    public static void main(String[] args) {
        TestCache testCache = new TestCache();
        testCache.demo01();
        
    }

    public void demo01() {
        LOG.info("方案1:超级构造函数");
        ICache cache = new CacheImplV1(10, 100L,
         20, -0L, 
         0L, 0L);
        LOG.info(cache.toString());
    }
   
}

方案点评

作为程序员,完成任务是第一位的,这版代码,是最直截了当,最简单粗暴的。构造函数中,包含了所有需要用户初始化的参数。用户使用起来,也是最麻烦的,不论自己需要初始化几个参数,所有参数都必须提供一个值。对于用户不需要的参数,常常会输入0,或者null值。这还是简化过的,只有6个参数,如果是十几个,几十个参数,用户估计会对这个类库,用脚投票。负责开发这个类库的程序员,也会被愤怒的码农,打个生活不能自理。

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

方案2:重叠式构造函数(稍稍人性化)

CacheImplV2:第二版代码

注意,这个类的构造函数个数,高达5个。如果需要初始化的参数再多点,构造函数的个数也会呈指数级增长。

package com.geekarchitect.patterns.builder.demo03;

import java.util.Map;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/4
 */
public class CacheImplV2 implements ICache {
    /**
     * 初始化容量,必须
     */
    private final int initialCapacity;
    /**
     * 最大数量,必须
     */
    private final long maximumSize;
    private final Map cacheMap = null;
    /**
     * 并行等级。决定segment数量的参数
     */
    private int concurrencyLevel = -1;
    /**
     * 最大权重
     */
    private long maximumWeight = -1L;
    /**
     * 写操作后失效时间
     */
    private long expireAfterWriteNanos = -1L;
    /**
     * 访问操作后失效时间
     */
    private long expireAfterAccessNanos = -1L;

    public CacheImplV2(int initialCapacity, long maximumSize) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
    }

    public CacheImplV2(int initialCapacity, long maximumSize, int concurrencyLevel) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
        this.concurrencyLevel = concurrencyLevel;
    }

    public CacheImplV2(int initialCapacity, long maximumSize, int concurrencyLevel, long maximumWeight) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
        this.concurrencyLevel = concurrencyLevel;
        this.maximumWeight = maximumWeight;
    }

    public CacheImplV2(int initialCapacity, long maximumSize, int concurrencyLevel, long maximumWeight, long expireAfterWriteNanos) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
        this.concurrencyLevel = concurrencyLevel;
        this.maximumWeight = maximumWeight;
        this.expireAfterWriteNanos = expireAfterWriteNanos;
    }

    public CacheImplV2(int initialCapacity, long maximumSize, int concurrencyLevel, long maximumWeight, long expireAfterWriteNanos, long expireAfterAccessNanos) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
        this.concurrencyLevel = concurrencyLevel;
        this.maximumWeight = maximumWeight;
        this.expireAfterWriteNanos = expireAfterWriteNanos;
        this.expireAfterAccessNanos = expireAfterAccessNanos;
    }

    @Override
    public String toString() {
        return "CacheImplV1{" +
                "initialCapacity=" + initialCapacity +
                ", maximumSize=" + maximumSize +
                ", cacheMap=" + cacheMap +
                ", concurrencyLevel=" + concurrencyLevel +
                ", maximumWeight=" + maximumWeight +
                ", expireAfterWriteNanos=" + expireAfterWriteNanos +
                ", expireAfterAccessNanos=" + expireAfterAccessNanos +
                '}';
    }

    @Override
    public void put(K key, V value) {
    }

    @Override
    public V get(K key) {
        return null;
    }
}
package com.geekarchitect.patterns.builder.demo03;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/6
 */
public class TestCache {
    private static final Logger LOG = LoggerFactory.getLogger(TestCache.class);

    public static void main(String[] args) {
        TestCache testCache = new TestCache();
        
        testCache.demo02();
        
    }
    
    public void demo02() {
        LOG.info("方案2:重叠构造函数");
        ICache cache = new CacheImplV2(10, 100L, 20);
        LOG.info(cache.toString());
    }

    
}

方案点评

可以看到,这版代码,类库的架构师,被打怕了,稍微有点用户思维了。为了让用户少输入一些0或者Null值,他们不顾自己的辛苦,一口气增加了5个构造函数。其实,这还远远不够。类有6个参数,前面两个是必须的,后面四个是可选的,有多少种组合,数学学得好的程序员,不难算出来,可能出现的情况有多少种吧。由于构造函数受到参数类型的限制,不能实现所有的情况,但是可以出现的情况也非常大。这还仅仅是6个参数,如果是十几个,几十个,那需要的构造函数个数,数量相当可观。即使真的定义出来,使用类库的普通程序员,估计也会眼晕,头大。不自觉地抬起自己的脚,走你,又是一对群殴。

小知识,对于上面多个构造函数重载,还被人起了一个专用名词,叫重叠构造器( telescoping constructor)。

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

被群殴的架构师

方案3:javaBean方式(自助餐模式)

CacheImplV3:第三版代码

注意,这次只有一个构造函数,其他的参数,都增加了相应的set方法。

package com.geekarchitect.patterns.builder.demo03;

import java.util.Map;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/4
 */
public class CacheImplV3 implements ICache {
    /**
     * 初始化容量,必须
     */
    private final int initialCapacity;
    /**
     * 最大数量,必须
     */
    private final long maximumSize;
    private final Map cacheMap = null;
    /**
     * 并行等级。决定segment数量的参数
     */
    private int concurrencyLevel = -1;
    /**
     * 最大权重
     */
    private long maximumWeight = -1L;
    /**
     * 写操作后失效时间
     */
    private long expireAfterWriteNanos = -1L;
    /**
     * 访问操作后失效时间
     */
    private long expireAfterAccessNanos = -1L;

    public CacheImplV3(int initialCapacity, long maximumSize) {
        this.initialCapacity = initialCapacity;
        this.maximumSize = maximumSize;
    }

    public void setConcurrencyLevel(int concurrencyLevel) {
        this.concurrencyLevel = concurrencyLevel;
    }

    public void setMaximumWeight(long maximumWeight) {
        this.maximumWeight = maximumWeight;
    }

    public void setExpireAfterWriteNanos(long expireAfterWriteNanos) {
        this.expireAfterWriteNanos = expireAfterWriteNanos;
    }

    public void setExpireAfterAccessNanos(long expireAfterAccessNanos) {
        this.expireAfterAccessNanos = expireAfterAccessNanos;
    }

    @Override
    public String toString() {
        return "CacheImplV2{" +
                "initialCapacity=" + initialCapacity +
                ", maximumSize=" + maximumSize +
                ", cacheMap=" + cacheMap +
                ", concurrencyLevel=" + concurrencyLevel +
                ", maximumWeight=" + maximumWeight +
                ", expireAfterWriteNanos=" + expireAfterWriteNanos +
                ", expireAfterAccessNanos=" + expireAfterAccessNanos +
                '}';
    }

    @Override
    public void put(K key, V value) {
    }

    @Override
    public V get(K key) {
        return null;
    }
}
package com.geekarchitect.patterns.builder.demo03;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/6
 */
public class TestCache {
    private static final Logger LOG = LoggerFactory.getLogger(TestCache.class);

    public static void main(String[] args) {
        TestCache testCache = new TestCache();
        testCache.demo03();
    }
    
    public void demo03() {
        LOG.info("方案3:JavaBean方式");
        CacheImplV3 cache = new CacheImplV3(10, 100L);
        cache.setConcurrencyLevel(20);
        LOG.info(cache.toString());
    }
}

方案点评

这次,类库的架构师,脑瓜一转,不再和构造函数过不去了。只实现了一个构造函数,对必须的参数进行初始化。其他的参数,都提供了相应的set方法。对于使用类库的人,需要哪些参数,请自觉的调用相应的set方法即可。由原来的食堂改自助餐了。方案表面上貌似完美,一切都好像那么简单清爽,风轻云淡。真这么简单吗,如果是这样,还要我们后面的建造者模式干什么。其实,这里面潜在的危机还很多。

1,参数校验问题:由于用户调用set方法,对参数进行初始化,数量,次序都是不可控的,再加上参数与参数之间,可能存在依赖关系(用了这个,就必须用另外一个)或者互斥关系(用了这个,就不能用另外一个)。set方式的参数初始化,类库组件无法在用户正式使用这个组件之前,对所有参数的合理性,进行校验。全靠使用者自觉。这不就是裸奔吗。

2,线程安全问题:由于调用set方法,是多次调用,导致在多线程环境下使用组件时,会造成组件的状态不一致,存在线程安全问题。对象多次调用这种模式,是无法实现一个不可变的类,也就是线程安全的类(链式调用可以救场)。

所以,这个貌似风轻云淡的方案,实则危机四伏,下面,建造者模式该登场了,只不过不是GOF版的,而是Joshua Bloch版的。

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

方案4:Joshua Bloch改进的建造者模式(主流方式)

MyCache类:Joshua Bloch版建造者模式

这个类是关键,注意以下6点:

1,MyCache 类里面的静态内部类MyCacheBuilder才是神来之笔。

2,MyCache 类的构造函数是private的(有点像单例模式)

3,MyCache 类的参数,没有提供set方法

4,MyCache 类中必须的参数,添加final关键字

5,静态内部类MyCacheBuilder的set方法,不同于常规的set方法,注意它的返回值

6,静态内部类MyCacheBuilder的build方法,非常重要。

package com.geekarchitect.patterns.builder.demo04;

import java.util.Map;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/6
 */
public class MyCache {
    /**
     * 初始化容量,必须
     */
    private final int initialCapacity;
    /**
     * 最大数量,必须
     */
    private final long maximumSize;
    /**
     * 并行等级。决定segment数量的参数
     */
    private int concurrencyLevel = -1;
    /**
     * 最大权重
     */
    private long maximumWeight = -1L;
    /**
     * 写操作后失效时间
     */
    private long expireAfterWriteNanos = -1L;
    /**
     * 访问操作后失效时间
     */
    private long expireAfterAccessNanos = -1L;
    private MyCache(MyCacheBuilder myCacheBuilder) {
        this.initialCapacity = myCacheBuilder.initialCapacity;
        this.maximumSize = myCacheBuilder.maximumSize;
        this.concurrencyLevel = myCacheBuilder.concurrencyLevel;
        this.maximumWeight = myCacheBuilder.maximumWeight;
        this.expireAfterWriteNanos = myCacheBuilder.expireAfterWriteNanos;
        this.expireAfterAccessNanos = myCacheBuilder.expireAfterAccessNanos;
    }

    @Override
    public String toString() {
        return "MyCache{" +
                "initialCapacity=" + initialCapacity +
                ", maximumSize=" + maximumSize +
                ", concurrencyLevel=" + concurrencyLevel +
                ", maximumWeight=" + maximumWeight +
                ", expireAfterWriteNanos=" + expireAfterWriteNanos +
                ", expireAfterAccessNanos=" + expireAfterAccessNanos +
                '}';
    }

    public void put(K key, V value) {
    }

    public V get(K key) {
        return null;
    }

    public static class MyCacheBuilder {
        /**
         * 初始化容量,必须
         */
        private final int initialCapacity;
        /**
         * 最大数量,必须
         */
        private final long maximumSize;
        private final Map cacheMap = null;
        /**
         * 并行等级。决定segment数量的参数
         */
        private int concurrencyLevel = -1;
        /**
         * 最大权重
         */
        private long maximumWeight = -1L;
        /**
         * 写操作后失效时间
         */
        private long expireAfterWriteNanos = -1L;
        /**
         * 访问操作后失效时间
         */
        private long expireAfterAccessNanos = -1L;

        public MyCacheBuilder(int initialCapacity, long maximumSize) {
            this.initialCapacity = initialCapacity;
            this.maximumSize = maximumSize;
        }

        public MyCacheBuilder setConcurrencyLevel(int concurrencyLevel) {
            this.concurrencyLevel = concurrencyLevel;
            return this;
        }

        public MyCacheBuilder setMaximumWeight(long maximumWeight) {
            this.maximumWeight = maximumWeight;
            return this;
        }

        public MyCacheBuilder setExpireAfterWriteNanos(long expireAfterWriteNanos) {
            this.expireAfterWriteNanos = expireAfterWriteNanos;
            return this;
        }

        public MyCacheBuilder setExpireAfterAccessNanos(long expireAfterAccessNanos) {
            this.expireAfterAccessNanos = expireAfterAccessNanos;
            return this;
        }

        public MyCache build() {
            return new MyCache(this);
        }
    }
}
package com.geekarchitect.patterns.builder.demo04;

import com.geekarchitect.patterns.builder.demo03.TestCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/7/6
 */
public class TestMyCache {
    private static final Logger LOG = LoggerFactory.getLogger(TestMyCache.class);
    public static void main(String[] args) {
        TestMyCache testMyCache=new TestMyCache();
        testMyCache.demo01();
    }

    public void demo01() {
        MyCache myCache = new MyCache.MyCacheBuilder(10, 100L)
                .setConcurrencyLevel(20)
                .setMaximumWeight(30L).build();
        LOG.info(myCache.toString());
    }
}

方案点评

这次,类库的架构师,认真的学习了Joshua Bloch(Java集合框架创始人,Google的首席Java架构师)在2001年就出版的《Effective java》这本书,参考了它里面的建造者设计模式实现方案。Joshua Bloch在书中,专门强调了,他采用的就是GOF在《Design Patterns: Elements of Reusable Object-Oriented Software》中介绍的建造者(Builder)设计模式,虽然这两个方案相差度有点大,大到令很多初次接触的人,一度对自己的智商产生怀疑。

Joshua Bloch版本的建造者模式,解决了上面重叠式构造函数以及javaBean方式的缺陷,实现了一个线程安全的创建对象的方式,而且可以在正式使用组件之前,对参数进行合法性校验。所以被广泛的应用在很多框架中。

小知识,链式调用不仅仅是为了美观,简洁,其实更重要的是把多次调用变为一次调用,保障线程安全。

下面我们进入guava框架的源码,看看google的架构师,是如何实现这个缓存组件的。

方案5:Google架构师的方案(源码解析)

Cache类:抽象产品

package com.google.common.cache;

@GwtCompatible
public interface Cache {
    @Nullable
    V getIfPresent(Object var1);

    V get(K var1, Callable<? extends V> var2) throws ExecutionException;

    ImmutableMap getAllPresent(Iterable<?> var1);

    void put(K var1, V var2);

    void putAll(Map<? extends K, ? extends V> var1);

    void invalidate(Object var1);

    void invalidateAll(Iterable<?> var1);

    void invalidateAll();

    long size();

    CacheStats stats();

    ConcurrentMap asMap();

    void cleanUp();
}

LocalManualCache类:具体产品

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

CacheBuilder类:工厂类

注意,这个类原本有三百多行,为了突出重点,我只保留了重点内容。

package com.google.common.cache;

import com.google.common.annotations.GwtCompatible;


@GwtCompatible(
    emulated = true
)
public final class CacheBuilder {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
    private static final int DEFAULT_EXPIRATION_NANOS = 0;
    private static final int DEFAULT_REFRESH_NANOS = 0;
    
    static final int UNSET_INT = -1;
    boolean strictParsing = true;
    int initialCapacity = -1;
    int concurrencyLevel = -1;
    long maximumSize = -1L;
    long maximumWeight = -1L;
    Weigher<? super K, ? super V> weigher;
    Strength keyStrength;
    Strength valueStrength;
    long expireAfterWriteNanos = -1L;
    long expireAfterAccessNanos = -1L;
    long refreshNanos = -1L;
    Equivalence keyEquivalence;
    Equivalence valueEquivalence;
    RemovalListener<? super K, ? super V> removalListener;
    Ticker ticker;
    Supplier<? extends StatsCounter> statsCounterSupplier;

    CacheBuilder() {
        this.statsCounterSupplier = NULL_STATS_COUNTER;
    }

    public static CacheBuilder newBuilder() {
        return new CacheBuilder();
    }

    public  Cache build() {
        this.checkWeightWithWeigher();
        this.checkNonLoadingCache();
        return new LocalManualCache(this);
    }

    
    public CacheBuilder initialCapacity(int initialCapacity) {
        Preconditions.checkState(this.initialCapacity == -1, "initial capacity was already set to %s", this.initialCapacity);
        Preconditions.checkArgument(initialCapacity >= 0);
        this.initialCapacity = initialCapacity;
        return this;
    }

    int getInitialCapacity() {
        return this.initialCapacity == -1 ? 16 : this.initialCapacity;
    }

    public CacheBuilder concurrencyLevel(int concurrencyLevel) {
        Preconditions.checkState(this.concurrencyLevel == -1, "concurrency level was already set to %s", this.concurrencyLevel);
        Preconditions.checkArgument(concurrencyLevel > 0);
        this.concurrencyLevel = concurrencyLevel;
        return this;
    }

    int getConcurrencyLevel() {
        return this.concurrencyLevel == -1 ? 4 : this.concurrencyLevel;
    }

    public CacheBuilder maximumSize(long maximumSize) {
        Preconditions.checkState(this.maximumSize == -1L, "maximum size was already set to %s", this.maximumSize);
        Preconditions.checkState(this.maximumWeight == -1L, "maximum weight was already set to %s", this.maximumWeight);
        Preconditions.checkState(this.weigher == null, "maximum size can not be combined with weigher");
        Preconditions.checkArgument(maximumSize >= 0L, "maximum size must not be negative");
        this.maximumSize = maximumSize;
        return this;
    }

    @GwtIncompatible
    public CacheBuilder maximumWeight(long maximumWeight) {
        Preconditions.checkState(this.maximumWeight == -1L, "maximum weight was already set to %s", this.maximumWeight);
        Preconditions.checkState(this.maximumSize == -1L, "maximum size was already set to %s", this.maximumSize);
        this.maximumWeight = maximumWeight;
        Preconditions.checkArgument(maximumWeight >= 0L, "maximum weight must not be negative");
        return this;
    }
    
    long getMaximumWeight() {
        if (this.expireAfterWriteNanos != 0L && this.expireAfterAccessNanos != 0L) {
            return this.weigher == null ? this.maximumSize : this.maximumWeight;
        } else {
            return 0L;
        }
    }

     Weigher getWeigher() {
        return (Weigher)MoreObjects.firstNonNull(this.weigher, CacheBuilder.OneWeigher.INSTANCE);
    }

    @GwtIncompatible
    public CacheBuilder weakKeys() {
        return this.setKeyStrength(Strength.WEAK);
    }

    
    @GwtIncompatible
    public CacheBuilder weakValues() {
        return this.setValueStrength(Strength.WEAK);
    }

    @GwtIncompatible
    public CacheBuilder softValues() {
        return this.setValueStrength(Strength.SOFT);
    }

    CacheBuilder setValueStrength(Strength strength) {
        Preconditions.checkState(this.valueStrength == null, "Value strength was already set to %s", this.valueStrength);
        this.valueStrength = (Strength)Preconditions.checkNotNull(strength);
        return this;
    }

    Strength getValueStrength() {
        return (Strength)MoreObjects.firstNonNull(this.valueStrength, Strength.STRONG);
    }

    public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) {
        Preconditions.checkState(this.expireAfterWriteNanos == -1L, "expireAfterWrite was already set to %s ns", this.expireAfterWriteNanos);
        Preconditions.checkArgument(duration >= 0L, "duration cannot be negative: %s %s", duration, unit);
        this.expireAfterWriteNanos = unit.toNanos(duration);
        return this;
    }

    long getExpireAfterWriteNanos() {
        return this.expireAfterWriteNanos == -1L ? 0L : this.expireAfterWriteNanos;
    }

    public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) {
        Preconditions.checkState(this.expireAfterAccessNanos == -1L, "expireAfterAccess was already set to %s ns", this.expireAfterAccessNanos);
        Preconditions.checkArgument(duration >= 0L, "duration cannot be negative: %s %s", duration, unit);
        this.expireAfterAccessNanos = unit.toNanos(duration);
        return this;
    }

    long getExpireAfterAccessNanos() {
        return this.expireAfterAccessNanos == -1L ? 0L : this.expireAfterAccessNanos;
    }

    @GwtIncompatible
    public CacheBuilder refreshAfterWrite(long duration, TimeUnit unit) {
        Preconditions.checkNotNull(unit);
        Preconditions.checkState(this.refreshNanos == -1L, "refresh was already set to %s ns", this.refreshNanos);
        Preconditions.checkArgument(duration > 0L, "duration must be positive: %s %s", duration, unit);
        this.refreshNanos = unit.toNanos(duration);
        return this;
    }

    long getRefreshNanos() {
        return this.refreshNanos == -1L ? 0L : this.refreshNanos;
    }

    public CacheBuilder ticker(Ticker ticker) {
        Preconditions.checkState(this.ticker == null);
        this.ticker = (Ticker)Preconditions.checkNotNull(ticker);
        return this;
    }

    Ticker getTicker(boolean recordsTime) {
        if (this.ticker != null) {
            return this.ticker;
        } else {
            return recordsTime ? Ticker.systemTicker() : NULL_TICKER;
        }
    }   
}

方案点评

guava框架的真正的架构师,在设计cache组件时,在Joshua Bloch版本的建造者模式的基础上,进行了改进。

1,增加了产品接口,贯彻了面向接口编程。

2,具体工厂并没有采用静态内部类,而是一个专门的工厂类。思路和Joshua Bloch版本的建造者模式基本一致。

最后,我们就看看真正的,最原始的建造者设计模式,也就是GOF原著里面的设计模式,是什么样的。

建造者(Builder Pattern)模式定义

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》

将一个复杂对象的构建和它的表示分离,使得同样的构建过程,可以创建不同的表示。

——Gof《设计模式:可复用面向对象软件的基础》

这个定义里面,关键词是复杂对象,构建和表示。

复杂对象(complex object)

这里的复杂对象,可以这样理解

1,这个对象可能在创建时,需要初始化很多属性。

比如,我们上面的案例,就属于这种情况,大部分框架中使用的建造者模式,都是为了解决这个问题。

2,这个对象在创建时,可能包含多个部件,需要一步步创建。

比如,GOF原著里面的案例,属于这种情况,我在现有的框架中,目前还没有发现这种用法,这也是我在这个设计模式上,背离GOF的原因。

因为构建复杂对象的过程比较繁琐,需要通过建造者模式来完成。

构建(construction)

构建(construction)和我们经常见到的实例化(instantiation),这两个单词有何不同。我们平时说的创建一个类的对象,也叫实例化(instantiation)一个类,侧重的是根据类,创建对象的过程,再精确一点,是侧重构造函数的执行。而构建对象,则覆盖的范围更广,它包含实例化,初始化参数,注入依赖的对象等等,在使用对象之前进行的准备工作,都可以纳入到构建的过程中,如下:

实例化过程:类->对象

构建化过程:类->对象->初始化参数->注入依赖关系->开始使用。

表示(representation)

表示(representation)何解?我认为这个是定义里面,最难理解的点,包含GOF的原著,对于这个词,没有特定明确的说明,但是根据他们的讲解及相关案例的推断,可以这样理解表示,可能不太准确。

表示(representation)是复杂对象展示给用户的一种形式。

比如,在RTF阅读器案例中,复杂对象是RTF格式的文档,而这个文档,通过不同的转换器,可以展示为纯文本(ASCIItext),Tex格式文本(TexText),文本组件(TextWidget)。

综上所述,建造者模式,将复杂对象的构建与表示分离,最终目的是在相同的构建过程下,可以构建出不同的对象表示。

建造者模式通用类图和代码

类图

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

接口及类

IBuilder:抽象工厂

ConcreteBuilderA:具体工厂

ConcreteBuilderB:具体工厂

ConcreteProductA:具体产品

ConcreteProductB:具体产品

Director:导演类

Client:客户方

TestBuilder:测试类

交互图

JDK框架大佬 google首席JAVA架构师,是这样用设计模式的

相关代码,已上传到github上,大家自行下载即可。

原始建造者模式的奇怪之处

1,它里面没有“抽象产品“,也就是没有产品类的接口,按照作者的原话,因为每个具体工厂,创建的对象太复杂,差异太大,所以没必要,也可能没办法建立一个统一的产品接口,也就是抽象产品。所以,它里面只有具体产品,没有抽象产品。

2,抽象工厂里面没有真正的工厂方法,你有没有发现,上面类图中,IBuilder类里面并没有getProduct方法,这个方法才是真正的工厂方法,其他的两个方法,都是用来创建产品部件的,虽然也是创建对象的,但创建的不是最终的,真正的产品。而具体工厂里面,有各自的工厂方法。这是因为它里面没有抽象产品,所以抽象工厂里面,也没法定义通用的工厂方法。

3,导演类(如果你从github下载了源码,研究过就会发现)不是面向接口编程。导演类,必须根据具体工厂,生产具体产品。

所以说,从上面的几点可以看出,原始的建造者模式是一个多么奇怪的设计模式,这可能就是它没有流行起来,反而被Joshua Bloch改进的版本,超越的原因吧。也是我为什么,对于这个设计模式,选择临时背离GOF的原因。

至此,建造者模式的基础知识,我们就分享到这里。对于建造者模式在各种知名框架,类库里面的应用,我后面有专门的分享。

极客架构师,专注架构师成长,我们下期见。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章