Android设计模式之Builder模式

前言 : Android中的AlertDialog用的就是builder设计模式,图片加载控件Universal-Image-Loader的初始化配置也是使用的builder设计模式,之后接触的Glide以及Picasso都是使用了Builder模式,膜拜。此外,经典的网络架构,okhttp中也使用了Builder模式,所以平时我们还是接触不少创建型模式。(如果我们想要使用的控件或者是对象时,不是new出来的,而是build出来的,多半使用的就是Builder设计模式)那么他们为什么使用builder设计模式,什么情况下我们应该考虑使用builder设计模式呢?我看了几篇关于Builder设计模式的文章,其中有三篇都是定义然后介绍一下模式的有点还有缺点,以及使用场景,说实话,我看完之后是懵逼的.有一篇写的还不错,本文也主要是借鉴,文章末尾,我会给出出处.

模式的定义

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

UML图

UML图

角色介绍

Product 产品类 : 产品的抽象类。

Builder : 抽象类, 规范产品的组建,一般是由子类实现具体的组件过程。

ConcreteBuilder : 具体的构建器.

Director : 统一组装过程(可省略)。

应用场景: 复杂对象的创建,内部包含多个部件或者零件

优点:使用灵活,易于扩展;不用关心内部实现细节,只注重结果

缺点:产生多余的Builder对象,消耗内存。

下面举例来说明一下,毕竟例子是比较好的体现方式.

如果我们有一个类Student,他有很多的属性,但是仅仅姓名和学号是必须赋值的,其他的属性都是可选项,比如像下面代码中所示.

public class Student {

private final int stuId;//必须

private final String name;//必须

private final int age;//可选

private final int gender;//可选

private final int address;//可选

...//还有很多可选属性

}

那么我们怎么来创建一个Student对象呢?我们看到每个属性都用final来修饰了,说明每个属性都要在构造方法中被初始化,我们又必须提供各种参数数量的构造方法,我们看如下代码

public class Student {

private final int stuId;//必须

private final String name;//必须

private final int age;//可选

private final int gender;//可选

private final String address;//可选

public Student(int stuId,String name){

this(stuId,name,0,1,"");

}

public Student(int stuId,String name,int age){

this(stuId,name,age,1,"");

}

public Student(int stuId,String name,int age,int gender){

this(stuId,name,age,gender,"");

}

public Student(int stuId,String name,int age,int gender,String address){

this.stuId = stuId;

this.name = name;

this.age = age;

this.gender = gender;

this.address = address;

}

}

这样做确实可以解决我们的需求,但这还是可选参数不多的情况,如果有很多可选参数,我们就必须要写很多个构造函数,这将导致代码的可读性和维护性变差,更重要的是,当我们要用到这个类的时候会感觉无从下手,我到底应该用哪个构造方法呢?应该用两个参数的构造方法还是用三个参数的呢?如果我用两个参数的构造方法,那么可选参数的默认值是多少?

更棘手的是,如果我只想给Student对象设置address属性而不设置age和gender属性的话怎么办?我们显然还得再继续添加构造方法,或者我们只能调用全参的构造方法,然后给age和gender属性设置个默认值。

还有一点,我们看到stuId,age,gender都是int类型的,那么我们在创建Student对象时,哪一个int类型的对象代表stuId,哪一个代表age,这还进一步增加了使用成本。

那么我们还有没有其他的办法?答案是有!我们可以只设置一个默认的无参构造方法,然后给每个属性添加getter和setter方法,代码如下

public class Student {

private int stuId;//必须

private String name;//必须

private int age;//可选

private int gender;//可选

private String address;//可选

public Student(){

}

public int getStuId() {

return stuId;

}

public void setStuId(int stuId) {

this.stuId = stuId;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public int getGender() {

return gender;

}

public void setGender(int gender) {

this.gender = gender;

}

public String getAddress() {

return address;

}

public void setAddress(String address) {

this.address = address;

}

}

这种方法看上去可读性和维护性比较好,当我们使用这个类的时候只需要创建一个空的对象并且设置我们需要的属性就可以了。比如这样:

Student stu = new Student();

stu.setStuId(1);

stu.setName("小明");

stu.setAge(12);

这样做有两个问题,第一个问题是我们的stu对象没有一个创建完毕的标识,上面的stu对象我们设置了三个属性,但当别人看到这段代码时,他不确定这个stu对象是只需要这三个属性还是当时作者忘了写完整,除非所有的属性都给set上,别人才能确保你这个对象创建完毕;另一个问题是任何人都可以在我们创建好的基础上继续改变它,也就是继续给它set新的属性或者删除某个已经set的属性,这就会使我们的stu对象具有可变性,这会引起潜在的风险。

好在我们还有第三种方法,那就是builder设计模式了。

public class Student {

private final int stuId;//必须

private final String name;//必须

private final int age;//可选

private final int gender;//可选

private final String address;//可选

private Student(StudentBuilder builder){

this.stuId = builder.stuId;

this.name = builder.name;

this.age = builder.age;

this.gender = builder.gender;

this.address = builder.address;

}

public int getStuId() {

return stuId;

}

public String getName() {

return name;

}

public int getAge() {

return age;

}

public int getGender() {

return gender;

}

public String getAddress() {

return address;

}

public static class StudentBuilder{

private final int stuId;

private final String name;

private int age;

private int gender;

private String address;

public StudentBuilder(int stuId,String name){

this.stuId = stuId;

this.name = name;

}

public StudentBuilder setAge(int age){

this.age = age;

return this;

}

public StudentBuilder setGender(int gender){

this.gender = gender;

return this;

}

public StudentBuilder setAddress(String address){

this.address = address;

return this;

}

public Student build(){

return new Student(this);

}

}

}

值得注意的几点:

1.Student的构造方法是私有的,也就是说我们不能直接new出Student对象

2.我们又将Student的属性用final修饰了,并且我们在构造方法中都为他们进行了初始化操作,我们只提供了getter方法

3.使用builder模式构造出来的对象有更好的可读性,等下我们会看到

4.StudentBuilder的属性中只给我们必须的属性添加的final修饰,所以我们必须在StudentBuilder的构造方法中为他们初始化

使用builder设计模式完美的解决了方法一和方法二的不足,并且兼具他们的优点:具有必填属性和可选属性的区分,更重要的是:可读性很强。唯一的不足是我们要在StudentBuilder中重复的写一遍Student中的属性。

好,现在我们来创建一个Student对象吧

public Student getStudent(){

return new Student.StudentBuilder(1,"小明")//必填属性在构造方法中赋值

.setAge(1)//设置可选属性 年龄

.setGender(1)//设置可选属性 性别 默认1为男

.build();//对象构建完毕的标识,返回Student对象

}

非常优雅有木有?他是一个链式的调用,我们可以1行代码就搞定,更重要的是,他的可读性非常强,而且通过build()我们可以很明确的告诉别人我们的Student已经创建完毕。

builder设计模式非常灵活,一个builder可以创建出各种各样的对象,我们只需要在build()之前调用set方法来为我们的对象赋值。

builder模式另一个重要特性是:它可以对参数进行合法性验证,如果我们传入的参数无效,我们可以抛出一个IllegalStateException异常,但是我们在哪里进行参数合法性验证也是有讲究的:那就是在对象创建之后进行合法性验证。我们修改StudentBuilder的build()方法

public Student build(){

Student student = new Student(this);

if (student.getAge()>120){

throw  new IllegalStateException("年龄超出限制");

}

return student;

}

为什么要先创建对象,再进行参数验证?因为我们的StudentBuilder是线程不安全的,如果我们先进行参数验证后创建对象,那么创建对象的时候对象的属性可能已经被其他线程改变了,例如下面的代码就是错误的

/**

* 错误的

*/

public Student build(){

if (age>120){

throw  new IllegalStateException("年龄超出限制");

}

return new Student(this);

}

最后总结一下builder设计模式:当我们的类中有很多属性的时候,更重要的是有很多可选属性的时候,我们就可以使用builder设计模式,因为这样不仅可以使我们的类使用起来很优雅,而且还可以给我们的对象一个创建完成的标识,即build()方法。此种多是链式调用,用起来方便,赏心悦目。

另外,可以看下AlertDialog的源码,这里我就不贴出来了,太多。

本文来源: http://blog.csdn.net/nugongahou110/article/details/50395698

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容