建造者(Builder)设计模式,对我来说,就像一个熟悉的陌生人:她是那样的熟悉,亲切,友善,她曼妙修长的身姿(链式调用),出现在了JDK、Spring框架、Mybatis框架、Hibernate框架等数不清的一流框架和类库里面。然而,当我想走近她,亲近她,了解她时,才发现她就像一个在外漂泊多年,离家已经很远的少女,早已不是她本初的模样。
Google首席Java架构师
GOF,四位设计模式前辈,创造了她。然而,她在java语言的发展中,受到JDK大佬级人物
Joshua Bloch(Java集合框架创始人,Google的首席Java架构师)的宠爱,在其《Effective java》中的大力推广和演绎,让她在java语言领域,特别是框架和类库开发领域,备受青睐,光彩照人。同时,她自身在发展过程中,女大十八变,由孤芳自赏,遥不可及的高冷范,蜕变为魅力四射,担当主角的御姐范。在两者之间,我思之再三,最后毅然决然的选择了后者,为了她,我宁愿选择,暂时的背离GOF 四位老前辈。
大家好,欢迎关注极客架构师,极客架构师,专注架构师成长,我是码农老吴。
本期是《架构师基本功之设计模式》的第10期,在上期,我一口气,分享了工厂系列模式里面的三个模式,简单工厂模式,工厂方法模式,抽象工厂模式。在本期,我将分享创建型设计模式里面的最后一个设计模式,建造者(builder)设计模式,也有翻译为生成器设计模式,创建者设计模式。
框架和类库架构师的烦恼
假如你是guava类库的架构师,你该如何做
方案1:超级大的构造函数(简单粗暴)
方案2:重叠式构造函数(稍稍人性化)
方案3:javaBean方式(自助餐模式)
方案4:Joshua Bloch改进的建造者模式(主流方式)
方案5:Google架构师的方案(源码解析)
建造者(Builder Pattern)模式定义
建造者模式通用类图和代码
原始建造者模式的奇怪之处
对于普通的程序员,特别是java程序员,使用框架或者类库是家常便饭。而使用框架或者类库里面的组件之前,一个常见的工作,就是配置一堆参数。一个参数,几个参数,几十个参数,都有可能。了解每个参数的作用,了解如何配置合适的参数,是java程序员的看家本领。
而换个角度,对于框架或者类库的架构师(大多数有野心的程序员,都想开发一个自己的框架),则需要考虑,如何让框架或者类库的使用者,便捷,高效,安全的配置参数。这也是创建者(Builder)设计模式,人见人怜,大行其道的一个重要原因。
上图是构建guava 的cache组件,可以配置的参数。
以google公司的guava类库cache组件为例,从上面的截图,可以看出来,CacheBuilder里面,有15个左右的参数,我们在使用cache组件的时候,一般不需要全部配置,可以根据需要,配置合适的几个参数即可。
假如你是这个类库的架构师,你该如何设计这个组件,让用户可以便捷,高效,安全地配置所需的参数呢。而且组件还可以轻松地对用户配置的参数,进行参数校验。同时,在多线程环境下,还需要保证组件的线程安全。我想对于一个即使没有多少经验的java程序员,至少能想到以下两到三个方案。
为了让这个案例,更符合设计模式知识的讲解,我们从上面的十几个参数里面,挑选出几个简单的,大家熟悉的属性,进行案例展示,并且约定里面有两个参数是必须的,其他是可选的。
我们看代码。
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);
}
注意两点
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;
}
}
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个参数,如果是十几个,几十个参数,用户估计会对这个类库,用脚投票。负责开发这个类库的程序员,也会被愤怒的码农,打个生活不能自理。
注意,这个类的构造函数个数,高达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)。
被群殴的架构师
注意,这次只有一个构造函数,其他的参数,都增加了相应的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版的。
这个类是关键,注意以下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的架构师,是如何实现这个缓存组件的。
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();
}
注意,这个类原本有三百多行,为了突出重点,我只保留了重点内容。
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
guava框架的真正的架构师,在设计cache组件时,在Joshua Bloch版本的建造者模式的基础上,进行了改进。
1,增加了产品接口,贯彻了面向接口编程。
2,具体工厂并没有采用静态内部类,而是一个专门的工厂类。思路和Joshua Bloch版本的建造者模式基本一致。
最后,我们就看看真正的,最原始的建造者设计模式,也就是GOF原著里面的设计模式,是什么样的。
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)。
综上所述,建造者模式,将复杂对象的构建与表示分离,最终目的是在相同的构建过程下,可以构建出不同的对象表示。
类图
接口及类
IBuilder:抽象工厂
ConcreteBuilderA:具体工厂
ConcreteBuilderB:具体工厂
ConcreteProductA:具体产品
ConcreteProductB:具体产品
Director:导演类
Client:客户方
TestBuilder:测试类
相关代码,已上传到github上,大家自行下载即可。
1,它里面没有“抽象产品“,也就是没有产品类的接口,按照作者的原话,因为每个具体工厂,创建的对象太复杂,差异太大,所以没必要,也可能没办法建立一个统一的产品接口,也就是抽象产品。所以,它里面只有具体产品,没有抽象产品。
2,抽象工厂里面没有真正的工厂方法,你有没有发现,上面类图中,IBuilder类里面并没有getProduct方法,这个方法才是真正的工厂方法,其他的两个方法,都是用来创建产品部件的,虽然也是创建对象的,但创建的不是最终的,真正的产品。而具体工厂里面,有各自的工厂方法。这是因为它里面没有抽象产品,所以抽象工厂里面,也没法定义通用的工厂方法。
3,导演类(如果你从github下载了源码,研究过就会发现)不是面向接口编程。导演类,必须根据具体工厂,生产具体产品。
所以说,从上面的几点可以看出,原始的建造者模式是一个多么奇怪的设计模式,这可能就是它没有流行起来,反而被Joshua Bloch改进的版本,超越的原因吧。也是我为什么,对于这个设计模式,选择临时背离GOF的原因。
至此,建造者模式的基础知识,我们就分享到这里。对于建造者模式在各种知名框架,类库里面的应用,我后面有专门的分享。
极客架构师,专注架构师成长,我们下期见。
留言与评论(共有 0 条评论) “” |