距离上次写博客的距离也快一个月了,最近打算跟大家分享一下建造者模式,其实这个模式以前也介绍过,当时只是介绍它在android项目中广泛被应用于创建对象的链式调用,并没有深入的去说明其中的逻辑性,由于这次跟王大哥讨论建造者模式,我还是决定写一篇博客记录一下自己对建造者模式的理解,各位可以提出不同的观点,相互交流
1:Builder 设计模式
1.1为什么要用建造者模式
建造者模式是一种很常见的设计模式,其实在我们android项目中基本上都使用它的变种的模式用于创建对象,这也是谷歌提倡的,暂且不说这个,单说builder顾名思义就是建造的意思,说起建造我们首先想到盖房子,第一步,打桩,第二步,修地基,第三步,砌砖等等,最后封顶,建造者模式跟盖房子本质是一样的的:即流程不变,但每个流程的具体实现是变化的,张二家地基要深一些,墙砌的厚一些,瓦用红色的,李四家由于靠着悬崖房子不能建的太厚,墙身偏薄,等等,一句话具体的流程怎么走不管,但流程不会变化,不会增加也不会减少,这个是非常重要的,我们熟悉的歪楼事件,就是由于先建立楼房后建立停车场,这就是典型的颠倒顺序造成的。
同样的KFC做出来的东西,不论是全国哪家店做出来就都一个味,因为KFC内部有很严格的规定,做巨无霸有做巨无霸的流程,必须严格遵守,这样做出来的东西当然一致了。KFC就是采用了建造者模式!!
说了这么多,那到底什么才是建造者模式呢,官方给出的解释是:
将一个复杂对象的构建和表示分离,同样的构建过程可以创建不同的表示。
如何理解这句话呢,构建和表示分离?何为构建?何为表示?
想弄清楚这些东西,先看下文。
1.2:建造者模式的几个角色
先理清楚,建造者模式通常包括下面几个角色:
builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建部分,并不涉及具体的对象部件的创建。
ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。(1:完成产品对象的部分构建,2:返回产品实例)
Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。(严格按照流程生产产品)
Product:要创建的复杂对象
建造者模式优势:它将创建复杂对象的创建过程分离,无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。它关注如何一步一步创建一个的复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。
网上经典的建造者模式的例子就是做汉堡
肯德基做汉堡的过程都是有严格的规范的,不管是麦香鸡腿堡还是新奥尔良烤鸡腿堡,他们的制作步骤都是有严格规定,做汉堡的人既不能多做一步,也不能少做一步。对于不同的汉堡来说,虽然每一步加的料所有不同,但做汉堡的步骤都是一样的。因为有了对做汉堡过程的严格控制,因而全国所有的肯德基店做出来的汉堡味道都是一样的。
我没做过汉堡,只会做软件,下面我以我做软件的视角来剖析一下建造者的设计思想。
1.3:一款软件产品的建造过程
下面我们来看一款软件产品的建造过程,
首先创建一个软件开发流程的接口,该接口一般声明两种方法,一种生产各个部分的方法,一种返回复杂产品。
一款软件的实现需要前期编写需求文档,画界面,处理业务逻辑三个部分。
/**
* 复杂的产品,考虑到代码的可读性,这里只列举部分的成员属性
* 且作为测试成员属性都是String,真实的情况下有些属性是需要自定义的。
* Created by zew on 17/7/8.
*/
public class Product {
private String xuqiu;//需求
private String ui;//界面
private String luoji;//逻辑
public String getXuqiu() {
return xuqiu;
}
public void setXuqiu(String xuqiu) {
this.xuqiu = xuqiu;
}
public String getUi() {
return ui;
}
public void setUi(String ui) {
this.ui = ui;
}
public String getLuoji() {
return luoji;
}
public void setLuoji(String luoji) {
this.luoji = luoji;
}
}
这里我们抽象一个builder,它定义如何创建复杂对象的各个部件。
/**
* 抽象建造者
* Created by zew on 17/7/8.
*/
public interface Builder {
void buildXuqiu();//文档设计
void buildUi();//界面
void buildLuoji();//业务逻辑
Product getApp();//返回开发的产品
}
然后根据不同的软件我们可以创建不同的具体的创建者,譬如我这里的信用猫创建者用于创建信用猫,信用花创建者用于创建信用花,对于具体的建造者,有三个注意点1:具体完成接口builder来构建或者装配产品部件,2:定义明确所要完成的产品是什么东西,3:提供一个可以获取产品的接口
/**
* 信用猫builder具体的实现
* Created by zew on 17/7/8.
*/
public class CreditCatBuilder implements Builder{
Product p;
public CreditCatBuilder() {
p=new Product();
}
@Override
public void buildXuqiu() {
p.setXuqiu("建造信用猫需求文档");
}
@Override
public void buildUi() {
p.setUi("建造信用猫画UI");
}
@Override
public void buildLuoji() {
p.setLuoji("建造信用猫写逻辑");
}
//暴露可以获取产品的方法
@Override
public Product getApp() {
return p;
}
}
/**
* 信用花builder具体的实现
* Created by zew on 17/7/8.
*/
public class CreditHuaBuilder implements Builder{
Product p;
public CreditHuaBuilder() {
p=new Product();
}
@Override
public void buildXuqiu() {
p.setXuqiu("建造信用花设计需求文档");
}
@Override
public void buildUi() {
p.setUi("建造信用花画UI");
}
@Override
public void buildLuoji() {
p.setLuoji("建造信用花写逻辑");
}
//暴露可以获取产品的方法
@Override
public Product getApp() {
return p;
}
}
指挥者类,该类定一个construct()方法,该方法拥有一个抽象建造者作为参数,该方法内部实现了软件开发的具体流程。
/**
* 指挥类,该类定义一个construct方法,该方法拥有一个抽象建造者类型的参数这样可以通过范型把不同的建造者模式都可以传入
* Created by zew on 17/7/8.
*/
public class AppDirector {
//严格按照生产顺序生产产品
//完成需求文档
builder.buildXuqiu();
//完成ui
builder.buildUi();
//完成业务逻辑
builder.buildLuoji();
//返回产品
return builder.getApp();
}
}
功能测试
AppDirector appDirector = new AppDirector();
//传入信用猫builder
Product p = appDirector.construct(new CreditCatBuilder());
xinyongmao.setText(p.getXuqiu()+"==="+p.getUi()+"==="+p.getLuoji());
//传入信用花builder
Product p2 = appDirector.construct(new CreditHuaBuilder());
xinyonghua.setText(p2.getXuqiu()+"==="+p2.getUi()+"==="+p2.getLuoji());
这么两款优秀的app就被生产出来了。
总结:在建造者模式中,只需要创建一个指挥者对象,指挥者类建造者面向接口编程,只需要传入具体的建造者实例类型,指挥者对象就会一步一步构建完成的产品(逐步调用具体的建造者的buildX()方法),相同的构造过程可以完成不同的产品。
在刚刚的app生产实例中,如果想要生产不同的app,只需要更换具体的建造者对象即可,原有的代码无需修改,完全符合“开闭原则”。
写到这大家肯定都暗自高兴原来这就是建造者模式,为自己学习到一技之长而高兴,其实我要说的建造者模式只是开始,下面我们看下android中对建造者的变种。
2变种 Builder 模式:优雅的对象构建方式
这个链接是我当初写的一个博客,主要是跟大家分享一下,这种通过建造者模式可以优雅的创建对象,这个在java圣经Effective java中被热烈推荐的方式,而且在阅读过android源码的人都知道AlertDialog.builder就是使用这种灵活的方式,大家可以阅读。地址:http://note.youdao.com/share/?id=6060eb9b1063aa6663c13e3516b475b9&type=note
2.1:android源码中的Builder 模式:AlertDialog的源码
下面带着大家看AlertDialog的源码。
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setCancelable(false);
builder.setIcon(R.drawable.icon);
builder.setTitle("Title");
builder.setView(textEntryView);
builder.setPositiveButton("确认",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle(edtInput.getText());
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("");
}
});
builder.show();
}
通过类名就知道这是一个builder模式,通过builder对象来组装Dialog的各个部分,如,title,message,button,等等,这个在我们信用猫项目中对dialog的二次封装也是沿袭类似的思想,下面看AlertDialog的源码:
由于代码太多,我还是截图说明
由图中可以看出builder类可以设置AlertDialog的title,message,icon,
并且这些参数都被保存在AlertController.AlertParams 的成员变量P参数中,在调用builder类的create方法创建dialog的时候这些被保存的P中的参数会引用到Alert对象中去
下面我们看看图中的P.apply(dialog.mAlert);做了什么。
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
if (mOnKeyListener != null) {
dialog.setOnKeyListener(mOnKeyListener);
}
*/
}
在Apply方法中,只是将AlertParams的参数设置到AlertController中,例如将标题的message,title,icon等参数信息设置到内容视图上,我们获取到AlertDialog对象后通过show方法就可以显示对话框.
2.2:我对设计模式的理解
一点点进去最近去你会悄然发现,其实在AlertDialog的builder模式中并没有Director角色的出现,其实在很多场景中,android并没有完全的按照GOF在《设计模式:可服用面向对象软件的基础》一书中描述的经典模式实现来做,而是做了一些修改,这也是Effective java中提倡的一种创建对象的方式,也是我跟王大哥对builder模式的不同解读的很大一个区分点,android对builder模式做了一些修改,使得这个模式更加易用,这里的AlertDialog.builder同时扮演了上文提到的builder,CreditcatBuilder,AppDirector三个角色,简化了builder模式的设计。其实当模块比较稳定,不存在一些变化时没可以在经典的设计模式实现的基础上做一个精简,而不是照搬GOF上的经典的实现,更不要生搬硬套,使程序失去架构之美,真是由于这种灵活的运用设计模式,才让android源码很值得我们去学习。
3:感悟,偏执到底对一个技术开发和管理者到底是有利还是有弊
其实很多人都会这个词持有贬义,隐含着“较真”,“不懂变通”等感情色彩,但是对于做产品,它又显得是一个优点,我对偏执的态度是:我不觉得偏执是每一个技术人员的必须的能力,它是一个加分的能力,但不是一个必备的能力,而且具备这种能力的人一定要有配套的反省能力,我呢,由于性格上就具备这种能力,这对我尝试去做一些看起来很难完成任务的时候,肯定在性格上占了很大一块便宜,但是,从另一个角度来说,如果我是一名管理者,如果我选择的角度不是很明智,这对我个人和团队都会带来危害,总的来说就是如果你有反省能力和自我认知能力足够清醒,没有什么大的偏差的话,偏执对一个管理者是好事,对一个技术人,偏执向来都是利大于弊。。。