设计模式之builder模式

文章首发于个人博客

参考:

  • 经典 Builder / 变种 Builder 模式及自动化生成代码插件
  • 变种 Builder 模式:优雅的对象构建方式
  • 《Android源码设计模式解析与实战》
  • 重学设计模式 -- 建造者模式
  • 王者荣耀之「建造者模式」
  • 结合 Android 浅谈 Builder 模式
  • 人人都会设计模式---建造者模式--Builder

前言

builder模式也叫建造者模式, 也是Android中应用十分广泛的设计模式之一. 举个例子, 比如我们经常使用的框架okhttp和retrofit

OkHttpClient client = new Builder().writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(10, TimeUnit.SECONDS)
        .addInterceptor(interceptor)
        .cache(cache)
        .build();
复制代码
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .client(okhttpClient)
        .build();
复制代码

还有Android中AlerDialog

new AlertDialog.Builder(this).setTitle("这是标题")
        .setMessage("这是Message")
        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        })
        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        })
        .create()
        .show();
复制代码

所以我们赶快去探究一下这神器的builder模式吧

Builder模式介绍

该模式是为了将构建复杂对象的过程和它的部件解耦, 构建过程和部件都可以自由扩展, 降低耦合.举例来说, 如果一个类有很多种构造方法, 或者一个构造方法中需要传入很多的参数, 比如说需要十几个参数, 可想而知这样的构造函数是十分容易出错的.而如果使用set方法来依次传入参数, 又失去了链式调用的优雅性, 代码变得不连续.这个时候使用builder模式就非常适合了.

Builder模式的UML类图

这时候又要再次科普一下UML类图中各种图形的含义了(因为我每次都容易忘记). 每个矩形都代表一个类(包括class和interface), 比如中间的那个Builder, 第一层显示它的类名是Builder, 顶端的`《abstract》`表示这是一个抽象类, 第二层表示它有三个方法, '+'表示方法是public的, 三个方法的返回类型都是void. 空心三角形 + 实线 表示ConcreteBuilder是继承自Builder的 箭头 + 虚线 表示依赖关系, 箭头指向被使用者, 比如这里的意思就是说ConcreteBuilder依赖Product, 因为ConcreteBuilder在组装的时候还是需要一个Product的, 不然它把零件组装到哪里去呢?也就是说ConcreteBuilder持有Product的引用. 空心菱形 + 实线(或者箭头线) 表示聚合关系, 汽车对象由轮胎对象聚合而成,但是轮胎对象的生命期并不受汽车对象的左右。当汽车对象销毁时,轮胎对象也可以单独存在.在这里, Director中必定有一个变量能够指向Builder, 表示聚合关系.

经典的Builder模式

Product

/**
 * 计算机抽象类, 即Product角色
 */
public abstract class Computer {

    protected String mBoard;
    protected String mDisplay;
    protected String mOS;

    public Computer() {
    }

    public void setBoard(String board) {
        mBoard = board;
    }

    public void setDisplay(String display) {
        mDisplay = display;
    }

    public abstract void setOS();

    @Override
    public String toString() {
        return "Computer{"
                + "mBoard='"
                + mBoard
                + '\''
                + ", mDisplay='"
                + mDisplay
                + '\''
                + ", mOS='"
                + mOS
                + '\''
                + '}';
    }
}
复制代码

具体的Product

/**
 * 具体的Computer类
 */
public class Macbook extends Computer {

    public Macbook() {
    }

    @Override
    public void setOS() {
        mOS = "Mac OS X 10.10";
    }
}
复制代码

抽象Builder

/**
 * 抽象Builder类
 */
public abstract class Builder {

    public abstract void buildBoard(String board);

    public abstract void buildDisplay(String display);

    public abstract void buildOS();

    public abstract Computer create();

}
复制代码

ConcreteBuilder

/**
 * 具体的Builder类, MacbookBuilder
 */
public class MacbookBuilder extends Builder {
    private Computer mComputer = new Macbook();

    @Override
    public void buildBoard(String board) {
        mComputer.setBoard(board);
    }

    @Override
    public void buildDisplay(String display) {
        mComputer.setDisplay(display);
    }

    @Override
    public void buildOS() {
        mComputer.setOS();
    }

    @Override
    public Computer create() {
        return mComputer;
    }
}
复制代码

Director

/**
 * Director类, 负责构造Computer
 */
public class Director {

    Builder mBuilder;

    public Director(Builder builder) {
        mBuilder = builder;
    }

    public void construct(String board, String display) {
        mBuilder.buildBoard(board);
        mBuilder.buildDisplay(display);
        mBuilder.buildOS();
    }
}
复制代码

测试代码

public class Main {

    public static void main(String[] args) {
        Builder builder = new MacbookBuilder();
        Director pcDirector = new Director(builder);
        pcDirector.construct("英特尔主板", "Retina 显示器");
        Computer macBook = builder.create();
        System.out.println("Computer Info: " + macBook.toString());
    }
}
复制代码

输出结果:

Computer Info: Computer{mBoard='英特尔主板', mDisplay='Retina 显示器', mOS='Mac OS X 10.10'}
复制代码

可以看出, 经典的 Builder 模式重点在于抽象出对象创建的步骤,并通过调用不同的具体实现类从而得到不同的结果, 但是在创建过程中依然要传入多个参数, 不是很方便, 所以有了变种的Builder模式

变种的Builder模式

目前来说在 Android&Java 开发过程中经典的 Builder 模式使用的较少,一般广泛使用的是他的一个变种
在日常的开发中 Director 角色经常会被忽略,这样会相对的减少了构造的步骤而直接使用一个 Builder 来进行对象的组装
这里我要说的就是一种内部Builder并且能够链式调用的变种
我们就直接进入实战, 模仿AlertDialog写一个dialogfragment, 但是比AlertDialog更简单些.

/**
 * Created by mundane on 2018/3/10 下午6:03
 */

public class EasyDialogFragment extends DialogFragment implements OnClickListener {

    private static final String KEY_TITLE = "key_title";
    private String mTitle;
    private TextView mTvTitle;
    private View mBtnCancel;
    private View mBtnConfirm;

    interface OnClickListener {
        void onClick();
    }

    private OnClickListener mPositiveListener;

    private OnClickListener mNegativeListener;

    private void setPositiveListener(OnClickListener onClickListener) {
        mPositiveListener = onClickListener;
    }

    private void setNegativeListener(OnClickListener onClickListener) {
        mNegativeListener = onClickListener;
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_cancel:
                if (mNegativeListener != null) {
                    mNegativeListener.onClick();
                }
                break;
            case R.id.btn_confirm:
                if (mPositiveListener != null) {
                    mPositiveListener.onClick();
                }
                break;
        }
        dismiss();
    }

    public static final class Builder {
        private String title;
        private OnClickListener mPositiveListener;
        private OnClickListener mNegativeListener;

        public Builder() {
            title = "";
        }

        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        public Builder setPositiveButton(OnClickListener onClickListener) {
            mPositiveListener = onClickListener;
            return this;
        }

        public Builder setNegativeButton(OnClickListener onClickListener) {
            mNegativeListener = onClickListener;
            return this;
        }


        public DialogFragment build() {
            EasyDialogFragment dialogFragment = new EasyDialogFragment();
            Bundle bundle = new Bundle();
            bundle.putString(KEY_TITLE, title);
            dialogFragment.setArguments(bundle);
            dialogFragment.setPositiveListener(mPositiveListener);
            dialogFragment.setNegativeListener(mNegativeListener);
            return dialogFragment;
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        mTitle = bundle.getString(KEY_TITLE);
    }


    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 圆角背景
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        View rootView = inflater.inflate(R.layout.layout_easy_dialogfragment, container, false);
        mTvTitle = rootView.findViewById(R.id.tv_title);
        mTvTitle.setText(mTitle);
        mBtnCancel = rootView.findViewById(R.id.btn_cancel);
        mBtnConfirm = rootView.findViewById(R.id.btn_confirm);
        mBtnCancel.setOnClickListener(this);
        mBtnConfirm.setOnClickListener(this);
        return rootView;
    }
}
复制代码

现在在日常的开发中 Director 角色经常会被忽略,这样会相对的减少了构造的步骤而直接使用一个 Builder 来进行对象的组装,最关键的还是 Builder 通常为链式调用,它的每个 setter 方法都返回自身,也就是代码中的 return this, 这样就可以实现链式调用了。

使用EasyDialogFragment:

DialogFragment dialogFragment = new Builder().setTitle("这是标题")
        .setPositiveButton(new EasyDialogFragment.OnClickListener() {
            @Override
            public void onClick() {
                Toast.makeText(MainActivity.this, "确定", Toast.LENGTH_SHORT).show();
            }
        })
        .setNegativeButton(new EasyDialogFragment.OnClickListener() {
            @Override
            public void onClick() {
                Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show();
            }
        })
        .build();
dialogFragment.show(getSupportFragmentManager(), "");
复制代码

效果:

总结

最后总结一下builder模式的优缺点
优点:

  1. 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节
  2. 建造者独立, 容易扩展
  3. 链式调用使得代码更简洁、易懂 缺点: 会产生多余的builder对象以及Director对象, 消耗内存

最后

最后发现了一个自动生成builder模式的插件 InnerBuilder, 详细可以看这篇
本文代码地址: github.com/mundane7996…

你可能感兴趣的:(设计模式之builder模式)