建造者模式(
Builder Pattern
)简化了构建复杂对象的过程,除了简化复杂的对象的构建过程,最核心的思想是化整为零、按需分配。
先说如何简化构建过程,建造者模式就像是Google的模块化手机,通过各个零件的定制来完成一部手机,比如,我可以装两个摄像头,或者把添加多一块电池,只是换个模块就能完成。
化整为零、按需分配说的是如果要实现一个多变的对象,把这个多变对象的整体,分解成一小块一小块的,然后组装起来,还能按照需求决定哪些需要定制,怎么定制,比如,Android或者Ios中常用的Dialog
、Notification
等。
类图
类图不是目的,仅仅帮助理解
[图片上传失败...(image-b3bc3-1527174228599)]
IBuilder
是一个建造者接口,规范了建造的内容,可以有很多实现,比如,BuilderA
、BuilderB
,有一个重点就是化整为零的产品和Builder
之间是组合关系,有了建造者和产品我们就可以通过建造者来定制产品了,这时候Director
的作用就是规定构造产品的顺序或者一些固定的其他默认属性,比如构建的时候依赖顺序必须先是A后是B,那么可以通过Director
来控制,这里的Direcotr
和抽象工厂模式有点像。
聚合关系:对象和对象之间是
整体
和部分
的关系,部分
的生命周期可以超越整体
,这是弱关系。
组合关系:对象和对象之间是整体
和部分
的关系,部分
的生命周期不能超越整体
,这是强关系。
实例
先看看实例的类图。
类图不是目的,仅仅帮助理解
[图片上传失败...(image-473fe6-1527174228599)]
还是以手机为例,规定一个手机产品的创建流程,用于流水线生产,手机的生产需要生产:主板、CPU、内存、屏幕、外壳。现在要实现一部手机,可以这样做:
fun main(args: Array<String>)
{
val nexus5 = Phone("Google")
nexus5.cpu = "Google CPU"
nexus5.ram = "Google RAM"
nexus5.screen = "Google Screen"
nexus5.motherboard = "Google Motherboard"
nexus5.view = "Google Nexus5 View"
println(nexus5)
}
// Phone
/**
* 手机
* Created by Carlton on 2016/11/15.
*/
class Phone(val name: String)
{
/**
* cpu
*/
var cpu: String? = null
/**
* 内存
*/
var ram: String? = null
/**
* 屏幕
*/
var screen: String? = null
/**
* 主板
*/
var motherboard: String? = null
/**
* 外观
*/
var view: String? = null
override fun toString(): String
{
return "Phone(name='$name', cpu=$cpu, ram=$ram, screen=$screen, motherboard=$motherboard, view=$view)"
}
}
这样,创建了一只Nexus5
的手机,如果现在需要一只Nexus6
手机怎么做呢?重新创建一个Phone
实例,然后给属性赋值。如果Nexus6
手机的CPU
、RAM
和Nexus5
一样,那赋值的代码就重复了,不方便重用了。如果还需要一个苹果6手机,又得重新去实例化Phone
对象,如果再建造一个苹果7,CPU
、主板
都一样,就会重复做很次这些操作,关键问题还不在这里,关键问题是暴露了产品的具体信息,这样产品类就变得极其不稳定,后期修改产品类的时候很难维护,因为很多地方在修改属性,如果使用建造者包装一次,客户端就不知道产品内部的具体信息(只有建造者知道,这样就控制了产品类出现的次数),后面修改产品类的时候就比较轻松。
还有一个问题是如果对构造顺序有严格的要求,比如必须先建主板才能建cpu那么,上面这种方式就不能控制。建造者的实现:
fun main(args: Array<String>)
{
// 首先创建一个Google手机的建造者创建一个nexus5手机
val googleBuilder = GoogleBuilder()
googleBuilder.buildCpu("Google CPU")
googleBuilder.buildMotherboard("Google Motherboard")
googleBuilder.buildRam("Google RAM")
googleBuilder.buildScreen("Google Screen")
googleBuilder.buildView("Google View")
val director = Director(googleBuilder)
val nexus5 = director.build()
println(nexus5)
// 现在创建一个nexus6的手机,还是用nexus5的建造者,屏幕和外观不一样
googleBuilder.buildScreen("Google Big Screen")
googleBuilder.buildView("Google Big View")
println(Director(googleBuilder).build())
}
// 打印
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Screen, motherboard=Google Motherboard, view=Google View)
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Big Screen, motherboard=Google Motherboard, view=Google Big View)
/**
* 手机Builder
* Created by Carlton on 2016/11/15.
*/
interface IPhoneBuilder
{
/**
* 定制CPU
*/
fun buildCpu(cpu: String?)
/**
* 定制内容
*/
fun buildRam(ram: String?)
/**
* 定制屏幕
*/
fun buildScreen(screen: String?)
/**
* 定制主板
*/
fun buildMotherboard(motherboard: String?)
/**
* 定制视图
*/
fun buildView(view: String?)
/**
* 创建
*/
fun create(): Phone
}
// Google手机建造者
/**
* 谷歌手机建造者
* Created by Carlton on 2016/11/15.
*/
class GoogleBuilder : IPhoneBuilder
{
override fun create(): Phone
{
return phone
}
val phone = Phone("Google")
override fun buildCpu(cpu: String?)
{
phone.cpu = cpu
}
override fun buildRam(ram: String?)
{
phone.ram = ram
}
override fun buildScreen(screen: String?)
{
phone.screen = screen
}
override fun buildMotherboard(motherboard: String?)
{
phone.motherboard = motherboard
}
override fun buildView(view: String?)
{
phone.view = view
}
}
// 组装者
/**
* 组装者
* Created by Carlton on 2016/11/16.
*/
class Director(val builder: IPhoneBuilder)
{
/**
* 顺序建造
*/
fun build(cpu: String, ram: String, motherboard: String, screen: String, view: String): Phone
{
builder.buildMotherboard(motherboard)
builder.buildCpu(cpu)
builder.buildRam(ram)
builder.buildScreen(screen)
builder.buildView(view)
return builder.create()
}
/**
* 建造
*/
fun build(): Phone
{
return builder.create()
}
}
首先,客户端创建了一个Google
手机的建造者,并且分别建造了各个部件,然后拿到组装者去组装,组装的时候就可以按照一定的顺序来组装,或者在组装的时候做一些其他事情,接来下让建造者修改了其中两个部件屏幕和外观,然后造了一个新手机。这样做可以轻易的替换建造者,而其他部分代码不用修改来控制建造过程。
总结一下,建造者(IBuilder
)可以隐藏具体的产品建造过程,产品的消费者只需要拿到完整的产品,组装者(Director
)可以控制产品组装的流程,具体的产品的创造和实例化客户端根本不关心。建造者也提供了很强的扩展性,通过替换建造者或者修改某一个建造者,就能在背后影响产品的创造过程,而客户端也就是消费者并不知道,建造者把业务需求表现的差异化实现封装到了IBuilder
和Director
。
和工厂模式的区别
和工厂模式一样都是输入创建类型的设计模式,封装创建过程给消费者,从类图上可以看出来和抽象工厂模式很像,但是,之前说过,类图只是参考,学习设计模式主要是学习其思路,在思路上抽象工厂模式是直接创建一个产品,及时的就把产品创造出来了,而建造者模式是先准备和定制产品属性,最后通过build()
或者create()
来创建一个产品。建造者的创建过程可以由客户端来控制,在创建过程上比抽象工厂模式更加灵活,在概念上抽象工厂模式创建的是一个产品族,是一类整体,建造者模式中产品过程则是独立的个体。
建造者的变种
建造者的核心在想在于创建产品,由很小的一些块组成整体产品。所以一般情况下不需要使用标准的建造者格式,大多数时候建造顺序不重要,这样只需要一个Builder
类,连接口都可以不使用,这种情况在很多地方都应用,举个例子:
Java中的java.util.Calendar.Builder,是一个针对日历实例的建造者
[图片上传失败...(image-d31cc6-1527174228599)]
可以看到这里面有很多set
方法,这些方法就是在定制这个产品,你会发现不需要有任何的顺序或者必须要调用,一个产品匹配一个建造者,主要作用是简化了实例过程,因为需要设置的属性太多了!最后使用build
方法生产一个Calendar
实例,这是典型的使用方式。
设计模式不要局限于形式,而在于思想
几个其他的例子
用几个简单的例子来加深一下理解。
StringBuilder
在Java
中如果要对String
进行操作尽量使用StringBuilder
,原因是String
的连接等操作会产生新的实例,它是一个不可变的对象,比如,String str = "abc" + "bcd";
这里内存中会产生3个对象,"abc"、"bcd"
都是一个String
对象,然后连接后把引用给到str
。
那么,StringBuilder
在这里有什么作用呢?StringBuilder
里面是一个char
数组,如果用StringBuilder
来做应该是这个样子:
val stringBuilder = StringBuilder()
stringBuilder.append("abc")
stringBuilder.append("bcd")
val str = stringBuilder.toString()
同样的都是拼接"acb"和"bcd"
如果使用StringBuilder
的话我们只会在toString()
的时候创建一个String
,不是每一次都去创建好一个产品,然后做操作,这样就提高了性能:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
这里的toString()
跟build()、create()
是一个意思。用建造者模式,我们可以对产品各个情况先组建好,这里就是对字符各种操作先操作完成,最后一次输出完整的字符串对象。
GsonBuilder
Gson我相信都用过,是一个Json转对象,对象转Json的库,和FastJson一样,前者是Google的,后者是阿里的。先看一段代码:
……
public final class GsonBuilder
{
private Excluder excluder;
private LongSerializationPolicy longSerializationPolicy;
private FieldNamingStrategy fieldNamingPolicy;
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final List<TypeAdapterFactory> factories;
private final List<TypeAdapterFactory> hierarchyFactories;
private boolean serializeNulls;
private String datePattern;
private int dateStyle;
private int timeStyle;
private boolean complexMapKeySerialization;
private boolean serializeSpecialFloatingPointValues;
private boolean escapeHtmlChars;
private boolean prettyPrinting;
private boolean generateNonExecutableJson;
……
这里有很多很多很多的属性,这些属性关系到对Json的解析和处理方式,我们不可能每次解析Json的时候都去赋值这么多属性,所以看看使用建造者模式如何规避这个问题,GsonBuilder
在实例化的时候预先了一些默认值:
public GsonBuilder()
{
this.excluder = Excluder.DEFAULT;
this.longSerializationPolicy = LongSerializationPolicy.DEFAULT;
this.fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
this.instanceCreators = new HashMap();
this.factories = new ArrayList();
this.hierarchyFactories = new ArrayList();
this.dateStyle = 2;
this.timeStyle = 2;
this.escapeHtmlChars = true;
}
然后也提供了一些方法来修改部分属性,也就是建造者方法,这样可以通过GsonBuilder
来建造一个Gson
实例,而不用过多的去关注建造过程。当所有的属性都准备完成后,一次性输出产品:
public Gson create()
{
ArrayList factories = new ArrayList();
factories.addAll(this.factories);
Collections.reverse(factories);
factories.addAll(this.hierarchyFactories);
this.addTypeAdaptersForDate(this.datePattern, this.dateStyle, this.timeStyle, factories);
return new Gson(this.excluder, this.fieldNamingPolicy, this.instanceCreators, this.serializeNulls, this.complexMapKeySerialization, this.generateNonExecutableJson, this.escapeHtmlChars, this.prettyPrinting, this.serializeSpecialFloatingPointValues, this.longSerializationPolicy, factories);
}
Android中也有很多建造者的应用比如:android.support.v4.app.NotificationCompat.Builder、android.support.v7.app.AlertDialog.Builder
,为什么使用建造者,建造者又有哪些缺点,通过这些实例自己能够去理解才是最重要的。
总结
建造者模式和抽象工厂类似都是封装了产品的建造过程,区别是建造者模式是构建完后一次性输出完整的产品,抽象工厂创建实例的时候,直接就输出了完成的产品,相比之下,建造者可以定制和控制构建过程,建造者也简化了产品的创建过程。
不登高山,不知天之高也;不临深溪,不知地之厚也
感谢指点、交流、喜欢