结合 Android 浅谈 Builder 模式

·  阅读 3254
结合 Android 浅谈 Builder 模式

前言

Builder模式,对象创建型的设计模式。说起设计模式,可能会觉得有点高深莫测,其实不然,我们每天写代码都在多多少少的和各种各样的设计模式接触,只是没有察觉而已。这里就来说一说Builder模式。

Android中的Builder模式

在Android开发中,什么时候会用到Builder模式呢?其实很简单,就是当你想使用一个控件时或者是一个对象时,没有办法直接把他New 出来;那么这个控件(对象)的实现多半就是用到了Builder模式。

AlertDialog.Builder

    private void InitView() {
        //直接创建对象
        TextView mTextView = new TextView(this);
        Button mButton = new Button(this);

        // 用Builder模式创建Dialog
        AlertDialog.Builder builder=new 
                AlertDialog.Builder(this)
                .setTitle("My Dialog")
                .setMessage("This is Test Dialog")
                .setIcon(R.drawable.application_icon);
        AlertDialog dialog=builder.create();
    }

如上面的代码,我们可以按照普通的方式(new)创建TextView对象和Button对象;但是轮到AlertDialog时,却需要首先创建一个AlertDialog.Builder对象,然后通过这个Builder对象才能创建AlertDialog的一个实例。同样都是Android的控件,差距为什么这么大呢?(因为Dialog复杂呀!o(╯□╰)o)。

下面可以结合AlertDialog的源码简单分析一下。

    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }
  • 首先是他的构造方法,可以看到这个方法是用 protected 修饰的,这意味着除了AlertDialog的子类之外,其他类是无法访问这个方法的。AlertDialog的其他两个重载的构造方法也是用到protected关键字修饰,有兴趣的同学可以自己参考源码;因此,在Activity或者是Fragment里,我们是无法直接创建AlertDialog的实例的,而是需要通过Builder对象。
        public static class Builder {
        private final AlertController.AlertParams P;

        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
        public Builder setTitle(@Nullable CharSequence title) {
            P.mTitle = title;
            return this;
        }
        public Builder setMessage(@Nullable CharSequence message) {
            P.mMessage = message;
            return this;
        }
        public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }

    .....
}
  • Builder类是AlertDialog内部的一个静态类。在这个类里有一个很关键的属性P,可以关注一下,这个变量是final类型的;除此之外剩下的就是一系列的setxxx 方法,用于设置AlertDialog的不同属性,例如上面列举的三个方法,可以分别设置AlertDialog的Title,Message及 Icon 信息。在这些方法中,都是把参数直接传递给了之前所说的P这个实例,而且每一个方法的返回值都是Builder类自身,这样就方便开发者链式调用每一个方法,这样不仅写起来简单,而且读起来也很有逻辑感
        public AlertDialog create() {
            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
            // so we always have to re-set the theme
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }
  • 最后,在Builder类的create方法中完成了AlertDialog的创建;万变不离其宗,AlertDialog的实例化,还是通过new创建出来,并通过之前所说的实例P和dialog实例实现了某种关联(具体如何实现暂不展开讨论),总之就是把之前通过Builder方法设置的一系列参数都配置到了最终的AlertDialog之上。

OKHttp中的Request.Builder

对于OKHttp,相信大家都不陌生,在构造Request对象时,就用到了Request.Builder。顾名思义,这里也用到了Builder模式。

        findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv.setText("");
                loading.setVisibility(View.VISIBLE);
                OkHttpClient client = new OkHttpClient();
                Request.Builder builder = new Request.Builder()
                        .url(BASE_URL)
                        .method("GET", null);

                Request request = builder.build();
                Call mCall = client.newCall(request);
                mCall.enqueue(new MyCallback());
            }
        });

以上是一个很典型的关于OKHttp的使用方式,这里Request对象也不是直接创建,而是通过首先创建一个Request.Builder对象,再通过他的build方法创建出最终的request对象。
这里可以粗略的看一下Request类的源码。

public final class Request {
  private final HttpUrl url;
  private final String method;
  private final Headers headers;
  private final RequestBody body;
  private final Object tag;


  private 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;
  }
    // 省略部分....

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

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }


    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
     * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
     */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace websocket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
     * https}.
     */
    public Builder url(URL url) {
      if (url == null) throw new NullPointerException("url == null");
      HttpUrl parsed = HttpUrl.get(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the header named {@code name} to {@code value}. If this request already has any headers
     * with that name, they are all replaced.
     */
    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }


    /** Removes all headers on this builder and adds {@code headers}. */
    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    public Builder method(String method, RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }


    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}
  • 首先,Request是final类型的,因此他不会有子类;再有就它唯一的构造方法是private的;因此,对于开发者来说就无法通过普通的方法(通过new)创建一个Request对象了。

  • Builder类,是一个静态内部类,通过其构造方法可以看出,Request 默认的请求方式是GET请求方式,同时当我们在创建Builder对象时,如果没有提供url 参数时会抛出异常,这是合理的也是必须的,一个Http请求如果没有url那么一切都是空谈,这里抛出异常 十分必要。在method方法中,会根据参数修改具体的请求方法,同时会根据请求方法判断是否需要RequestBody。

  • 最后,通过build方法创建了Request,可以看到这里调用的就是Request唯一的构造方法,传递的参数就是当前Builder实例。这样创建的Request对象就是根据我们构造出来的Builder实例所量身定制的Request。便于下一步进行同步或异步的网络请求。

至此,你可能会有疑问,所谓的Builder模式有什么意义?

为什么Android系统中创建一个AlertDialog要这么复杂,像TextView一样,直接new出来一个实例然后set各种属性不也一样可用吗?
Request 对象的创建不使用Builder模式一样也是可以的呀?上面各种异常处理,方法执行用普通的方式也可实现,Builder模式的价值在哪里呢?

带着这些疑问,让我们去好好理解一下Builder模式。

Builder 模式

定义

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

适用场景

1.相同的方法,不同的执行顺序,产生不同的事件结果
2.多个部件或零件,都可以装配到同一个对象中,但是产生的运行结果又不相同
3.产品类非常复杂,或者产品中的调用顺序不同产生了不同的作用
4.需要初始化一个对象特别复杂的对象,这个对象有很多参数,且有默认值

看这样的概念也许有些抽象,下面还是通过代码来看看。在之前工厂方法模式中,我们用工厂方法模式列举了Mobike于Ofo 对象生成的例子。这里依旧以二者为例,看看用Builder模式怎么写。

public final class Bicycle {
    public static final int SHARED = 1;
    public static final int PRIVATE = 0;

    @IntDef({SHARED, PRIVATE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface bicycleType {
    }

    protected String color;
    protected String name;
    protected double charge;
    protected int number;
    protected int type;

    protected Bicycle(BicycleBuilder builder) {
        this.color = builder.color;
        this.name = builder.name;
        this.charge = builder.chager;
        this.number = builder.number;
        this.type = builder.type;
    }

    public static class BicycleBuilder {


        private String color;
        private String name;
        private double chager;
        private int number;
        private int type;

        public BicycleBuilder() {
            this.color = "黑色";
            this.name = "永久";
            this.chager = 0;
            this.number = 0;
            this.type = Bicycle.PRIVATE;
        }

        public BicycleBuilder setColor(String color) {
            this.color = color;
            return this;
        }

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

        public BicycleBuilder setCharge(double chager) {
            this.chager = chager;
            return this;
        }

        public BicycleBuilder setNumber(int number) {
            this.number = number;
            return this;
        }

        public BicycleBuilder setType(@bicycleType int type) {
            this.type = type;
            return this;
        }

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

    @Override
    public String toString() {
        String typeStr= type == SHARED ? "共享单车": "私人车辆";

        return "Bicycle{" +
                "color='" + color + '\'' +
                ", name='" + name + '\'' +
                ", charge=每分钟" + charge +"/元"+
                ", number=" + number +
                ", type=" + typeStr +
                '}';
    }
}

在这里Bicycle类包含5个特有的属性,同时将其构造方法设置为protected。通过BicycleBuilder 类来真正实现创建Bicycle的实例。这里BicycleBuilder的默认的构造方法,会创建一个普通的黑色永久牌私人自行车,而通过BicycleBuilder提供的几个方法我们便可以创建不同的Bicycle实例。比如下面这种实现:

public class BuilderPatternActivity extends AppCompatActivity {
    private TextView bike_result;
    private Bicycle mBicycle;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_builder_pattern);
        bike_result = V.f(this, R.id.bike_result);
    }

    /**
     * 普通自行车
     * @param view
     */
    public void NormalBike(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder();
        mBicycle=builder.build();
        updateView(mBicycle);
    }
    /**
     * 膜拜单车
     * @param view
     */
    public void Mobike(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder()
                .setColor("橙色")
                .setName("膜拜单车")
                .setCharge(1.0)
                .setNumber(10010)
                .setType(Bicycle.SHARED);
        mBicycle=builder.build();
        updateView(mBicycle);
    }
    /**
     * OFO 单车
     * @param view
     */
    public void Ofo(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder()
                .setColor("黄色")
                .setName("OFO单车")
                .setCharge(0.5)
                .setNumber(40010)
                .setType(Bicycle.SHARED);
        mBicycle=builder.build();
        updateView(mBicycle);
    }


    private void updateView(Bicycle mBicycle) {
        bike_result.setText("");
        bike_result.setText(mBicycle.toString());
    }
}

通过Bicycle.BicycleBuilder 提供的一系列set方法,我们创建了mobike实例和ofo单车实例。

这就是Builder模式,正如定义和使用场景中提到的那样,通过Builder模式,在同样的构建过程下,我们可以创建不同的结果;通常来说,我们要创建的对象是很复杂的,有很多参数,这些参数中有些是必须的,比如OKHttp中Request的url参数,有些参数又会有默认值;总之,Builder模式,一种对象创建型的设计模式;为我们创建对象提供了一种思路。

最后,再说一个使用了Builder模式的东西-RxJava。说到RxJava我们很容易想到观察者模式。不错,RxJava最核心的思想就是观察者模式;但是想一想我们使用RxJava的过程。

        ArrayList<String> datas = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            datas.add("item_" + i);
        }
        Observable.just(datas)
                .flatMap(new Func1<ArrayList<String>, Observable<String>>() {
                    @Override
                    public Observable<String> call(ArrayList<String> strings) {
                        return Observable.from(strings);
                    }
                })
                .map(new Func1<String, Integer>() {
                    @Override
                    public Integer call(String s) {
                        return s.hashCode();
                    }
                })
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer Integer) {
                        Log.e(MainActivity.class.getSimpleName(), "call---->" + Integer);
                    }
                });

如上代码所示,在subscribe 方法执行之前,通过各种各样的操作符,原始数据一个ArrayList变成了一个Integer类型的数据,也就是说我们使用操作符的过程,就是一个Builder模式构建的过程,直到生成我们最终需要的产品为止。这和Builder模式的定义以及使用场景是完全符合的。

Builder模式 VS 工厂方法模式

工厂模式一般都是创建一个产品,注重的是把这个产品创建出来就行,只要创建出来,不关心这个产品的组成部分。从代码上看,工厂模式就是一个方法,用这个方法就能生产出产品。

建造者模式也是创建一个产品,但是不仅要把这个产品创建出来,还要关系这个产品的组成细节,组成过程。从代码上看,建造者模式在建造产品时,这个产品有很多方法,建造者模式会根据这些相同方法但是不同执行顺序建造出不同组成细节的产品。

工厂模式关心整体,建造者模式关心细节

最后

现在回到我们之前提出的问题,Builder模式的意义是什么?看完之后你可能已经得到答案了,没有任何实质意义,Builder模式的使用并不会使我们的代码运行速度加快。设计模式总的来说就是对是封装、继承、多态和关联的反复使用;是一种编程技巧,让我们能写出高质量代码的技巧。

最后再说一句,严格来说本文讨论的Builder模式并不是标准意义上的Builder模式,在这里我们从Android源码的角度出发,简化了Builder模式,为了方便链式调用及习惯,舍弃了原本应有的Director角色。对正统的Builder模式感兴趣的同学可以再去深入研究。

分类:
Android
分类:
Android
收藏成功!
已添加到「」, 点击更改