Builder 模式

最近工作之余一直在断断续续的研究媒体选择库,在 GitHub 上搜了好多库对比看了看,在学习研究过程中发现其中都运用了 Builder模式,今天就一起学习一下 Builder模式,顺便看看它在 Android 源码中的应用。

我们在实际开发中,必然会遇到一些需求需要构建一个十分复杂的对象,譬如本人最近开发的项目中就需要构建一个媒体库选择器,类似微信和众多app中都有的图片、视屏资源选择器。这个选择器(Selector)是相对比较复杂的,它需要很多属性,比如:

  • 媒体资源类型: 图片/视屏
  • 选择模式: 单选/多选
  • 多选上限
  • 是否支持预览
  • 是否支持裁剪
  • 选择器UI风格样式
  • ......

通常我们可以通过构造函数的参数形式去写一个实现类

Selector(int type)

Selector(int type, int model)

Selector(int type, int model, int maxSize)

Selector(int type, int model, int maxsize, boolean isPreview)

......

再或者可以使用 getter 和 setter 方法去设置各个属性

public class Selector {
    private int type; // 媒体资源类型: 图片/视屏
    private int model; // 选择模式: 单选/多选
    private int maxSize; // 多选上限
    private boolean isPreview; // 是否支持预览
    
    private ...... // 更多属性就不一一举例了,大家脑部一下    

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getModel() {
        return model;
    }

    public void setModel(int model) {
        this.model = model;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public boolean isPreview() {
        return isPreview;
    }

    public void setPreview(boolean isPreview) {
        this.isPreview = isPreview;
    }

    .......
}

先分析一下这两种构建对象的方式:

第一种方式通过重载构造方法实现,在参数不多的情况下,是比较方便快捷的,一旦参数多了,就会产生大量的构造方法,代码可读性大大降低,并且难以维护,对调用者来说也是一种灾难;

第二种方法可读性不错,也易于维护,但是这样子做对象会产生不确定性,当你构造 Selector 时想要传入全部参数的时候,那你就必需将所有的 setter 方法调用完成之后才算创建完成。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实 Selector 对象并没有创建完成,另外,这个 Selector 对象也是可变的,不可变类的所有好处也将随之消散。

写到这里其实大家已经想到了,肯定有更好的办法去解决这个问题,是的,你猜对了, 今天我们的主角 Builder模式 就是为解决这类问题而生的。下面我们一起看看 Builder模式 是如何优雅的处理这类尴尬的。

模式的定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

模式的使用场景

  1. 相同的方法,不同的执行顺序,产生不同的事件结果时;
  2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;
  3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适;

化解上述尴尬的过程

Builder模式 属于创建型,一步一步将一个复杂对象创建出来,允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。还是上面同样的需求,使用 Builder模式 实现如下:

public class Selector {
    private final int type; // 媒体资源类型: 图片/视屏
    private final int model; // 选择模式: 单选/多选
    private final int maxSize; // 多选上限
    private final boolean isPreview; // 是否支持预览
    
    private final ...... // 更多属性就不一一举例了,大家脑部一下
    
    // 私有构造方法
    private Selector(SelectorBuilder selectorBuilder){
    this.type = selectorBuilder.type;
    this.model = selectorBuilder.model;
    this.maxSize = selectorBuilder.maxSize;
    this.isPreview = selectorBuilder.isPreview;
    ......

    /** 由于所有属性都是 final 修饰的,所以只提供 getter 方法 **/

    public int getType() {
        return type;
    }

    public int getModel() {
        return model;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public boolean isPreview() {
        return isPreview;
    }

    .......
    
    // Builder 方法
    public static class SelectorBuilder{
        private int type; // 媒体资源类型: 图片/视屏
        private int model; // 选择模式: 单选/多选
        private int maxSize; // 多选上限
        private boolean isPreview; // 是否支持预览
        
        private ...... // 更多属性就不一一举例了,大家脑部一下
        public SelectorBuilder() {
            // 设置各个属性的默认值
            this.type = 1;
            this.model = 1;
            this.maxSize = 9;
            this.isPreview = true;
            ......
        }

        public SelectorBuilder setType(int type){
            this.type = type;
            return this;
        }

        public SelectorBuilder setModel(int model) {
            this.model = model;
            return this;
        }

        public SelectorBuilder setMaxSize(int maxSize) {
            this.maxSize = maxSize;
            return this;
        }

        ...... // 全部的setter方法就省略了

        public Selector build() {
            return new Selector(this);
        }
    }
}

值得注意的是 Selector 的构造方法是私有的,并且所有属性都是 final 修饰的,是不可变属性,对调用者也只提供 getter 方法,SelectorBuilder 内部类可以根据调用者的具体需求随意接收任意多个参数,应为我们再 SelectorBuilder 的构造方法中为每一个参数都设置了默认值,即使调用者调用时漏传某个参数,也不会影响整个创建过程。当我们将我们需用的所有参数传入后,随后调用 build() 构造 Selector 对象,代码如下:

new Selector.SelectorBuilder()
            .setType(2)
            .setModel(2)
            .setMaxSize(3)
            . ......
            .build();

是不是很简洁?意不意外?惊不惊喜?你没看错,就是这么厉害!

Android源码中的模式实现

在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 :

// 显示基本的AlertDialog  
private void showDialog(Context context) {  
    AlertDialog.Builder builder = new AlertDialog.Builder(context);  
    builder.setIcon(R.drawable.icon);  
    builder.setTitle("Title");  
    builder.setMessage("Message");  
    builder.setPositiveButton("Button1",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button1");  
                }  
            });  
    builder.setNeutralButton("Button2",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button2");  
                }  
            });  
    builder.setNegativeButton("Button3",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button3");  
                }  
            });  
    builder.create().show();  // 构建AlertDialog, 并且显示
} 

优缺点

当然在代码世界,永远没有绝对的完美,我们只是在走向完美的道路上尽力去填补一个个坑而已。 Builder模式 有它的好处,给我们带来了方便,但同时也会牺牲一些美好,这是不可避免的。

优点
  • 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
  • 建造者独立,容易扩展;
  • 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
缺点
  • 会产生多余的Builder对象,消耗内存;
  • 对象的构建过程暴露。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容