Android设计模式之Builder模式

作者: 正阳Android | 来源:发表于2017-11-23 23:27 被阅读30次

    前言 : 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

    相关文章

      网友评论

        本文标题:Android设计模式之Builder模式

        本文链接:https://www.haomeiwen.com/subject/xmzzvxtx.html