流行框架源码分析(11)-Builder建造者设计模式

主目录见:Android高级进阶知识(这是总目录索引)
 今天我们正式来讲讲一些常用的设计模式在android中的应用,适当适时地应用设计模式,能使程序看起来更加优雅,今天这里的建造者模式是非常常用的一个设计模式,不仅在框架中或者在android的源码中都能见到这个设计模式,我们先来看他的定义:

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

这个定义并不抽象,所谓的复杂对象的构建与它的表示分离,就比如人这个对象,他有身高,体重,年龄等等表示,但是要构建不同的人这个动作和这些人这个对象的属性的表示可以用建造者模式分离,我们待会会举个例子。接着我们来看看这个设计模式的UML类图:


建造者模式

角色介绍:

  • Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
  • Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  • ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
  • Product:要创建的复杂对象。

一.目标

因为很多时候我们不知道什么时候该用什么设计模式,甚至有些时候根本没想过要用设计模式,我们今天的目标就是:
1.明白建造者模式怎么使用;
2.知道在哪些场景下会用到建造者模式。

二.模式讲解

我们刚才说了,建造者模式就是将一个复杂对象的构建和它的表示分离,那么这个是什么意思呢?我们先来说一个复杂对象的表示。

1.Product

public class Person_Product {
    private String name;    //名字
    private int age;        //年龄
    private double height;  //身高
    private double weight;  //体重
    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 double getHeight() {
        return height;
    }
    public void setHeight(double height) {
        this.height = height;
    }
    public double getWeight() {
        return weight;
    }
    public void setWeight(double weight) {
        this.weight = weight;
    }
}

可以看到这里的人这个对象可以用名字,年龄,身高,体重来表示。那么这个对象的构建呢?我们怎么构建出不同的人呢?如果没有建造者模式的情况下,我们可能这么做:

public Person_Product() {
        super();
    }
    
    public Person_Product(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public Person_Product(String name, int age, double height) {
        super();
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public Person_Product(String name, int age, double height, double weight) {
        super();
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }

我们这里可能创建出多个构造函数,因为我们可能只要创建拥有名字和年龄的人或者我们可能需要增加体重和身高这两个属性的人,所以最后我们的创建过程变成:

        Person_Product person_Product = new Person_Product();
        Person_Product person_Product2 = new Person_Product("java程序员", 35);
        Person_Product person_Product3 = new Person_Product("c++程序员", 27, 175);
        Person_Product person_Product4 = new Person_Product("python程序员", 30, 170, 130);

我们看到这个构建方法是构建出人这个对象了,但是我们不明白到底这些参数是什么意思,同时在Person这个对象里面需要创建多个构造函数,如果属性变多呢?那不是按照这个结果不是要很多个构造函数。现在我们换个思路,用建造者设计模式来重新改变下这个构建过程。

2.Builder

抽象建造者Builder对象主要是用来定义一个统一的构建过程,其实这个不是必须的,但是为了演示标准的建造者设计模式我们还是来创建这个类:

public String name; //名字
    public int age;     //年龄
    public double height;   //身高
    public double weight;   //体重

    public Builder setName(String name) {
        this.name = name;
        return this;
    }

    public Builder setAge(int age) {
        this.age = age;
        return this;
    }

    public Builder setHeight(double height) {
        this.height = height;
        return this;
    }
    
    public Builder setWeight(double weight) {
        this.weight = weight;
        return this;
    }
    public abstract void buildName();
    public abstract void buildAge();
    public abstract void buildWeight();
    public abstract void buildHeight();
    public abstract Person_Product getResult();

我们看到类里面有构建名字,年龄,体重,身高的方法。然后我们接下来就创建具体的建造者对象了。

3.ConcreteBuilder

public class ConcreteBuilder extends Builder{
    
    private Person_Product person_Product = new Person_Product();

    @Override
    public void buildName() {
        person_Product.setName(this.name);
    }

    @Override
    public void buildAge() {
        person_Product.setAge(this.age);
    }

    @Override
    public void buildWeight() {
        person_Product.setWeight(this.weight);
    }

    @Override
    public void buildHeight() {
        person_Product.setHeight(this.height);
    }
    
    @Override
    public Person_Product getResult(){
        return person_Product;
    }

}

具体建造者模式主要是将建造者的年龄,身高,体重等信息设置给了人对象即Product。最后就是我们的Director对象了,用于指导建造的过程。

4.Director

public class Director {

    public void construct(Builder builder){
        builder.buildName();
        builder.buildAge();
        builder.buildHeight();
        builder.buildWeight();
    }
}

我们看到这个方法就是调用我们的所有构建过程进行构建。那么我们看下这个设计模式是怎么调用的。

5.调用

    Builder builder = new ConcreteBuilder().setAge(12)
                .setName("android工程师")
                .setHeight(170)
                .setWeight(132);
        Director director = new Director();
        director.construct(builder);
        
        Person_Product person_Product = builder.getResult();
        System.out.println(person_Product.getName()+"年龄:"+person_Product.getAge()+"身高:"+person_Product.getHeight()+"体重:"+person_Product.getWeight());

我们看到构建的过程Builder链式构建,然后传给Director进行构建。随意Person这个对象的表示和构建是分开的,两者没有耦合。现在我们再根据OkHttp或者EventBus框架这些的变形用法,进行修改。这里我们将Director和Builder的角色跟Product的角色进行合并。具体代码如下:

6.改版后建造者模式

public class Person_Product {
    private String name;    //名字
    private int age;        //年龄
    private double height;  //身高
    private double weight;  //体重
    
    public Person_Product(){
        super();
    }
    
    public Person_Product(Builder builder){
        this.name = builder.name;
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
    }
    
    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 double getHeight() {
        return height;
    }
    public void setHeight(double height) {
        this.height = height;
    }
    public double getWeight() {
        return weight;
    }
    public void setWeight(double weight) {
        this.weight = weight;
    }
    
    public static class Builder{
        public String name; //名字
        public int age;     //年龄
        public double height;   //身高
        public double weight;   //体重

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setHeight(double height) {
            this.height = height;
            return this;
        }
        
        public Builder setWeight(double weight) {
            this.weight = weight;
            return this;
        }
        
        public Person_Product build(){
            Person_Product person = new Person_Product(this);
            return person;
        }
    }
}

我们看到Builder对象以一个内部类的形式出现,然后Director的Controuct()方法以构造函数的形式出现。这样的话我们的调用就变得非常简单:

    Person_Product.Builder builder = new Person_Product.Builder();
        Person_Product person = builder.setName("android工程师")
                .setAge(29)
                .setHeight(176)
                .setWeight(135)
                .build();

我们看到修改后的建造者模式写法更加简单了,而且想要set哪个属性就set哪个属性。

7.OkHttp中的建造者模式

我们来看OkHttp的使用方法:

Request requestBuilder = new Request.Builder().url("http://www.baidu.com").build();

我们看到这里的Request对象的构建跟我们上面改进后建造者的调用方法一致,所以我们来看下Request的构造函数:

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

我们看到跟我们上面的做法是一模一样的,都是利用的Builder来赋值Request的属性值。

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;
......
}

同时我们看到这个Builder也是Request的内部类。还有一些建造方法:

  public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

    public Builder removeHeader(String name) {
      headers.removeAll(name);
      return this;
    }
......

这里选择Builder里面的一些建造方法来看,我们发现做法跟我们是相同的。我们再来看看另外一个开源框架EventBus的建造者模式身影。

8.EventBus的建造者模式

同样的,我们先来看看EventBus的基本用法是怎么样的。

 EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

我们看到这里明显也是用了建造者模式,我们先来看看EventBus类的构造函数:

 EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

如我们所见,这里的构造函数也是利用了builder来赋值,我们再看看Builder类:

public class EventBusBuilder {
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;
......
}

我们看到这个builder跟EventBus里面对应的属性是一样的,且我们来看看builder的一些建造方法和build方法:

  /** Forces the use of reflection even if there's a generated index (default: false). */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }

    /** Enables strict method verification (default: false). */
    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
        this.strictMethodVerification = strictMethodVerification;
        return this;
    }

 public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    /** Builds an EventBus based on the current configuration. */
    public EventBus build() {
        return new EventBus(this);
    }

同样的,这个跟我们之前用过的方式是一样的,到这里我们应该很明白建造者的用法了。明显,如果一个对象的属性比较多比较复杂,那么在创建他的时候,我们可以采用建造者来灵活地建造,使我们程序更加优雅。当然了android里面还有很多利用到这个比如AlertDialog,GsonBuilder 等等,有兴趣大家可以都看看。
总结:今天我们讲了建造者设计模式,这个模式用的地方还是非常广的,而且这个建造者在LRouter这个框架里面也是有用到的,用法简单但是有效,希望大家能在适合这个模式的场景中使用到。

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

推荐阅读更多精彩内容