建造者模式(Builder)及其应用
转载请注明原博客地址:
其实建造者模式在我们平时写项目的时候我们经常看到,大部分人没仔细观察或者不熟悉建造者模式,才忽略了它,在我们常用的ImageLoader和Rxjava里面其实都运用了建造者模式
本篇博客主要讲解一下几个问题
- 什么是建造者模式
- 建造者模式的应用场景,小Demo及优缺点分析
- 建造者模式在Android源码中的体现
- 建造者模式在常见的开源框架中ImageLoader和Rxjava的体现
1)什么是建造者模式
概念
简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程。
类UML图
Builder:可以是一个接口或者一个抽象类,主要是统一标准,便于我们使用多态而已
ConcreteBuilder:Builder的实现类,主要负责组建Product。
导演类(Director):主要负责统一指挥构建组装Product,但是它是不知道Product里面具体是怎样做的。
Product:产品类,是我们需要建造的复杂对象,可以是抽象类也可以是具体类,视对象的复杂程度而定
2) 建造者模式的应用场景,小Demo及优缺点分析
假设我们现在是手机生产商,主要负责手机的组装,手机的品牌有挺多的,有索尼,三星,小米等品牌,同一个品牌的手机的分辨率,CPU,摄像头肯能一样也可能不一样,你会怎么做呢?
有人说用抽象工厂模式?
刚开始想这样也是挺好的?
对于不同的手机品牌,我们单独有一个工厂实现子类,接着再交给工厂子类自己去实现手机。
不过后面自己想了一下,工厂模式诞生的初衷只是负责生产对象,而不必要知道生产手机的细节,而题目中要求同一个品牌的手机的分辨率,CPU,摄像头,这不就要求我们需要生产手机的细节了吗?所以使用抽象工厂模式不是最好的选择
下面我们来看一下使用Builder模式设计的UML图
下面我们一起来看一下例子代码
抽象Product类
//抽象Product类
public abstract class Phone {
protected String mCPU;
protected String mCamera;
protected String mScreen;
protected String mSystem;
public abstract void setSystem();
public void setmCPU(String cpu){
this.mCPU=cpu;
}
public void setmCamera(String camera) {
this.mCamera = camera;
}
public void setmScreen(String screen) {
this.mScreen = screen;
}
}
具体Product类
/**
* ContreteProduct类
* @author xujun
*
*/
public class SonyPhone extends Phone {
@Override
public void setSystem() {
mSystem="Android";
}
@Override
public String toString() {
return "SonyPhone [mCPU=" + mCPU + ", mCamera=" + mCamera
+ ", mScreen=" + mScreen + ", mSystem=" + mSystem + "]";
}
}
抽象Builder类
/**
* 抽象Builder类
* @author xujun
*
*/
public abstract class Builder {
public abstract void builderScreen(String screen);
public abstract void buildCamera(String camrea);
public abstract void buildCpu(String cpu);
/**
* 获得我们建造的Product对象
* @return
*/
public abstract Phone creat();
public abstract void buildSystem();
}
Builder类实现类
public class SonyBuilder extends Builder {
SonyPhone sonyPhone;
public SonyBuilder() {
sonyPhone=new SonyPhone();
}
@Override
public void builderScreen(String screen) {
sonyPhone.setmScreen(screen);
}
@Override
public void buildCamera(String camrea) {
sonyPhone.setmCamera(camrea);
}
@Override
public void buildCpu(String cpu) {
sonyPhone.setmCPU(cpu);
}
@Override
public Phone creat() {
return sonyPhone;
}
@Override
public void buildSystem() {
sonyPhone.setSystem();
}
}
Director类
public class Director {
Builder mBuilder;
String mScreen="1920x720";
String mCPU="双核";
String mCamera="默认品牌摄像头";
public Director(Builder builder){
mBuilder=builder;
}
//在这个方法里面调用builder相应的方法
public void construct(String camera,String screen,String cpu){
mBuilder.buildCamera(camera);
mBuilder.builderScreen(screen);
mBuilder.buildCpu(cpu);
mBuilder.buildSystem();
}
public void construct(String camera){
this.construct(camera, mScreen, mCPU);
}
public void construct(){
this.construct(mCamera,mScreen,mCPU);
}
}
public class TestBuilder {
public static void main(String[] str){
Builder builder = new SonyBuilder();
//导演持有builder的引用
Director director = new Director(builder);
director.construct("索尼摄像头", "1280x720像素", "4核因特尔CPU");
Phone phone = builder.creat();
System.out.println(phone.toString());
director.construct("索尼摄像头");
Phone phone2 = builder.creat();
System.out.println(phone2.toString());
director.construct();
Phone phone3 = builder.creat();
System.out.println(phone3.toString());
}
}
运行以上测试程序,将可以看到以下的输出结果
SonyPhone [mCPU=4核因特尔CPU, mCamera=索尼摄像头, mScreen=1280x720像素, mSystem=Android]
SonyPhone [mCPU=双核, mCamera=索尼摄像头, mScreen=1920x720, mSystem=Android]
SonyPhone [mCPU=双核, mCamera=默认品牌摄像头, mScreen=1920x720, mSystem=Android]
分析与讨论
看了上面的代码,你是不是觉得很烦,组装索尼手机品牌的不同型号,干嘛需要这么多类?其实这个只是表象而已,试想一下,如果有一天我们突然借了新的业务,需要组装小米手机?那我们只需要增加两个类就OK了,一个是XiaoMiPhone,另外一个是XiaoMiBuilder。
那假如有一天小米又推出了新的手机,比如是小米6,配置是16核,摄像头是索尼品牌,屏幕分辨率是2560*1920,我们基本是不用修改代码的,只需要在在director 传入相应的参数就OK了
director.construct("索尼摄像头", "2560*1920", "16核因特尔CPU");
- 有人会说这些事情,其实在Contrete类里面,自己就能完成了,根本就不需要Builder了?
其实如果Product管理的对象很少,有比较简单,确实是不需要Builder的,但我们Product里面需要管理的对象比较多,有比较复杂的话,如果把代码全部封装到Product里面,那岂不是破坏了Product的封装性,违背了单一责则的基本原则。
综上所述,建造者模式的 优点可概括如下
建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。
在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
其次,建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
在实际中的引用
其实在实际项目开发中,我们往往用不到上面的标准的Builder模式,因为我们的对象往往没有那么复杂,假如我们现在手机生产商只复制生产索尼手机,那Builder设计的UML图可以简略如下,去除Director。改为下面的链式调用。
SonyPhone phone=new SonyBuilder().buildCamera("索尼摄像机").buildCPU("8核").buildScreen("1280*720");
缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
适用场景
产品对象内部具有复杂的结构,产品对象包含多个成员属性,适用建造者模式可以隔离复杂对象的创建和适用,并使得相同的 创建过程可以创建不同的对象;
相同的方法,不同的执行顺序,产生不同的事件结果时;
3)Builder在Android中源码的体现。
还记得我们平时在使用Dialog的时候的这种用法吗?
new AlertDialog.Builder(this).setCancelable(true)
.setIcon(R.mipmap.app_logo).setMessage("温馨提醒").show();
哈哈,你是不是惊喜的发现这与我们上面简化的Builder模式几乎是一样的?没错,其实AlertDialog里面也使用了Builder模式
下面我们一起来看一下AlertDialog的源码
// AlertDialog
public class AlertDialog extends Dialog implements DialogInterface {
// Controller, 接受Builder成员变量P中的各个参数
private AlertController mAlert;
// 构造函数
protected AlertDialog(Context context, int theme) {
this(context, theme, true);
}
// 4 : 构造AlertDialog
AlertDialog(Context context, int theme, boolean createContextWrapper) {
super(context, resolveDialogTheme(context, theme), createContextWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
// 实际上调用的是mAlert的setTitle方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
// 实际上调用的是mAlert的setCustomTitle方法
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
// AlertDialog其他的代码省略
// ************ Builder为AlertDialog的内部类 *******************
public static class Builder {
// 1 : 存储AlertDialog的各个参数, 例如title, message, icon等.
private final AlertController.AlertParams P;
// 属性省略
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, int theme) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
// Builder的其他代码省略 ......
// 2 : 设置各种参数
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setIcon(int iconId) {
P.mIconId = iconId;
return this;
}
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewSpacingSpecified = false;
return this;
}
// 3 : 构建AlertDialog, 传递参数
public AlertDialog create() {
// 调用new AlertDialog构造对象, 并且将参数传递个体AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
// 5 : 将P中的参数应用的dialog中的mAlert对象中
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
}
}
可以看到,通过Builder来设置AlertDialog中的title, message, button等参数, 这些参数都存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams中包含了与之对应的成员变量。在调用Builder类的create函数时才创建AlertDialog, 并且将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中。
当我们调用show()函数的时候,它会调用create()函数把我们储存的参数设置到对应的View中,具体的create()方法这里就不贴出来了
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
4)Builder模式在ImageLoader和Rxjava中的体现
在ImageLoader中的体现
ImageLoaderConfiguration.Builder mBuilder = new ImageLoaderConfiguration
.Builder(this)
.threadPoolSize(3) //线程池内加载的数量
.threadPriority(Thread.NORM_PRIORITY - 2) //设置当前线程的优先级
.denyCacheImageMultipleSizesInMemory() //拒绝缓存同一图片的多个尺寸版本
.tasksProcessingOrder(QueueProcessingType.FIFO) //队列的排队算法,默认FIFO。FIFO表示先进先出,LIFO表示后进先出
.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) //你可以通过自己的内存缓存实现
.memoryCacheSize(2 * 1024 * 1024) //使用的内存大小
.memoryCacheSizePercentage(13) //使用的内存百分比
.memoryCacheExtraOptions(480, 800) //设置内存中图片的长宽
.diskCache(new UnlimitedDiskCache(imageCacheDir)) //自定义磁盘的路径
.diskCacheSize(50 * 1024 * 1024) //使用的磁盘大小
.diskCacheFileCount(100) //磁盘的文件数量上限
.imageDecoder(new BaseImageDecoder(false)) //对图片解码,如需缩放或旋转可在此处理
.writeDebugLogs() //打印调试日志。上线时需要去掉该方法
;
ImageLoader.getInstance().init(mBuilder.build());
在Rxjava中的体现
Network.getInstance()
.getApi()
.getSMS(phoneNumber,smsType)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(SMSBean securityBean) {
LUtils.e("" + securityBean.toString());
}
}, new Action1() {
@Override
public void call(Throwable throwable) {
EventBus.getDefault().post(err, GET_VERIFYCODE_ERR_SETPHONENUMBER);
}
});
到此今天博客的分析为止
题外话
以前为了应付面试,显得自己比较厉害,有时候一天下来生硬地记下了好几个设计模式,关于各种设计模式的应用场景和优缺点,那时候刚开始的一两天还记得,可过了几天,知识就还给了书本,基本都忘记了,更谈不上运用。记得有一次去面试,问了一个我以前一直认为很简单的单例模式,那时候叫我写出几种单例模式,我嗖的一声就写出类, 饿汉式,懒汉式(双重锁定,静态内部类),登记式。但当面试官问到为什么要使用Synchronized Class而不使用Synchronized this,我竟然搭不上,我知道this是代表当前实例的引用,class是锁住整个Class类,但就是区分不出来。后面回来复习了才知道。
从那以后,我开始静下心来,不再为了变得牛逼而去学习某样东西,这样目的性太强的话我们往往会带有一些浮躁,刚开始的一两个月可能你还能坚持下来了,可一旦你发现你学习了这几样东西,其实你还是跟大多数人一样,这时候的你可能就会浮躁,开始沉不住气了。继而自己开始懈怠,慢慢地你会发现你很慢就坚持静下心来学习了。
一句话,技术这种东西急不来,常常太用力的人跑不远,因为他们往往很难坚持下来。所以,从现在开始,静下心来,一步一个脚印,享受生活,享受编程给我们带来的乐趣。
卖一下广告,有兴趣了解设计模式的,了、可以阅读下面我的两篇博客
装饰者模式及其应用
观察者设计模式 Vs 事件委托(java)
转载请注明原博客地址:
例子源码下载地址: