icache(JDK框架大佬 google首席JAVA架构师)

建造者(Builder)设计模式,对我来说,就像一个熟悉的陌生人:她是那样的熟悉,亲切,友善,她曼妙修长的身姿(链式调用),出现在了JDK、Spring框架、Mybatis框架、Hibernate框架等数不清的一...

建造者(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<K,V> { 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<K, V> implements ICache<K, V> { /** * 初始化容量,必须 */ private final int initialCapacity; /** * 最大数量,必须 */ private final long maximumSize; private final Map<String, String> 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<String, String> cache = new CacheImplV1<String, String>(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<K, V> implements ICache<K, V> { /** * 初始化容量,必须 */ private final int initialCapacity; /** * 最大数量,必须 */ private final long maximumSize; private final Map<String, String> 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<String, String> cache = new CacheImplV2<String, String>(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<K, V> implements ICache<K, V> { /** * 初始化容量,必须 */ private final int initialCapacity; /** * 最大数量,必须 */ private final long maximumSize; private final Map<String, String> 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<String, String> cache = new CacheImplV3<String, String>(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<K, V> { /** * 初始化容量,必须 */ 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<K, V> { /** * 初始化容量,必须 */ private final int initialCapacity; /** * 最大数量,必须 */ private final long maximumSize; private final Map<String, String> 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<K, V>(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<String, String> 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;@GwtCompatiblepublic interface Cache<K, V> { @Nullable V getIfPresent(Object var1); V get(K var1, Callable<? extends V> var2) throws ExecutionException; ImmutableMap<K, V> 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<K, V> 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<K, V> { 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<Object> keyEquivalence; Equivalence<Object> valueEquivalence; RemovalListener<? super K, ? super V> removalListener; Ticker ticker; Supplier<? extends StatsCounter> statsCounterSupplier; CacheBuilder() { this.statsCounterSupplier = NULL_STATS_COUNTER; } public static CacheBuilder<Object, Object> newBuilder() { return new CacheBuilder(); } public <K1 extends K, V1 extends V> Cache<K1, V1> build() { this.checkWeightWithWeigher(); this.checkNonLoadingCache(); return new LocalManualCache(this); } public CacheBuilder<K, V> 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<K, V> 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<K, V> 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<K, V> 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; } } <K1 extends K, V1 extends V> Weigher<K1, V1> getWeigher() { return (Weigher)MoreObjects.firstNonNull(this.weigher, CacheBuilder.OneWeigher.INSTANCE); } @GwtIncompatible public CacheBuilder<K, V> weakKeys() { return this.setKeyStrength(Strength.WEAK); } @GwtIncompatible public CacheBuilder<K, V> weakValues() { return this.setValueStrength(Strength.WEAK); } @GwtIncompatible public CacheBuilder<K, V> softValues() { return this.setValueStrength(Strength.SOFT); } CacheBuilder<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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的原因。

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

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

  • 发表于 2022-12-20 20:58:58
  • 阅读 ( 167 )
  • 分类:科技

0 条评论

请先 登录 后评论
李赵薇
李赵薇

217 篇文章

你可能感兴趣的文章

相关问题