美文网首页
通过示例和源码阐述建造者模式

通过示例和源码阐述建造者模式

作者: 付凯强 | 来源:发表于2018-12-05 19:16 被阅读0次

    0. 序言

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

    1. 介绍

    • 建造者模式是一步一步创建一个复杂对象的创建型模式,允许在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。也就是它注重的是构建对象的构成,而不是构件对象过程中所需要的部件表示的细节。
    • 举例:贾跃亭会计要造一台FF,而一台FF需要轮胎、方向盘、发动机等部件。而贾跃亭关心的是如何组装这些零件为一辆车,而不是关心这个轮胎怎么造的,这个方向盘怎么造的。
    • 优点就是构建过程和部件可以自由扩展,两者之间的耦合降到最低。

    2. 场景

    • 相同的方法,不同的执行顺序,产生不同的结果。
      举例:街舞大家都看过,有时候是动手、动脚、转身,有时候是动脚、转身、动手等等,不同的行动方式,呈现不同的美。
    • 初始化一个对象特别复杂,如参数多。
      举例:初始化一个AlertDialog或者一个notification,有的需要标题、内容两项,有的需要标题、内容、进度条三项等等,这个时候如果通过构造方法初始化参数的话,会有很多种搭配组合,就要写很多构造方法,这个时候我们就可以使用建造者模式,需要哪个参数我们就拿每个参数对应的方法就行。

    3. UML类图(用PC观看)

    建造者模式类图.png

    角色介绍:
    ① Product产品类——产品的抽象类。
    ② Builder——抽象Builder类,规范产品的组件。
    ③ ConcreteBuilder——具体的Builder类,实现具体的组件过程。
    ④ Director——统一组装过程

    4. 场景一实现示例

    以跳舞为例:

    • 定义Dance抽象类,即Product角色:
    public abstract class Dance {
        protected abstract void hand();
        protected abstract void foot();
        protected abstract void turn_around();
    }
    
    • 定义Hiphop类,即具体的Dance类:
    public class Hiphop extends Dance {
    
        private List<String> mPerformList = new ArrayList<>();
    
        @Override
        protected void hand() {
            mPerformList.add("街舞:动动手");
        }
    
        @Override
        protected void foot() {
            mPerformList.add("街舞:动动脚");
        }
    
        @Override
        protected void turn_around() {
            mPerformList.add("街舞:转身");
        }
    
        public List<String> getPerformList() {
            return mPerformList;
        }
    }
    

    说明:为了能看出不同的跳舞顺序,我们增加了一个List集合和访问List集合的方法。

    • 定义抽象Builder类,即构建对象类:
    public abstract class Builder {
        public abstract void hand();
        public abstract void foot();
        public abstract void turn_around();
        public abstract Dance create();
    }
    
    • 定义构建对象的具体实例类DanceBuilder:
    public class DanceBuilder extends Builder {
    
        Dance mDance = new Hiphop();
    
        @Override
        public void hand() {
            mDance.hand();
        }
    
        @Override
        public void foot() {
            mDance.foot();
        }
    
        @Override
        public void turn_around() {
            mDance.turn_around();
        }
    
        @Override
        public Dance create() {
            return mDance;
        }
    }
    
    • 定义导演类,负责组装过程:
    public class Director {
        Builder mBuilder = null;
    
        public Director(Builder builder) {
            mBuilder = builder;
        }
    
        // 可以定义不同的顺序
        public void perform(){
            mBuilder.hand();
            mBuilder.foot();
            mBuilder.turn_around();
        }
    }
    
    • 进行测试:
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Builder builder = new DanceBuilder();
            Director director = new Director(builder);
            director.perform();
            Log.i("fukq","跳舞的顺序是:"+((Hiphop)(builder.create())).getPerformList());
        }
    }
    
    12-05 16:18:52.241 30387-30387/com.smartisan.builder I/fukq: 跳舞的顺序是:[街舞:动动手, 街舞:动动脚, 街舞:转身]
    

    说明:
    ① 通过DanceBuilder来构建Dance对象,而Director封装了构建复杂产品对象的过程,而DanceBuilder中有跳舞的不同的顺序,展示了场景一:相同的方法,不同的顺序,带来不同的结果。
    ② 开发中,Director会被省略,直接使用Builder来进行对象的组装,我们去掉Director类,修改下DanceBuilder类:

    public abstract class Builder {
        public abstract Dance setHand();
        public abstract Dance setFoot();
        public abstract Dance setTurn_around();
    }
    
    public class DanceBuilder extends Builder {
    
        Dance mDance = new Hiphop();
    
        @Override
        public Dance setHand() {
            mDance.hand();
            return mDance;
        }
    
        @Override
        public Dance setFoot() {
            mDance.foot();
            return mDance;
        }
    
        @Override
        public Dance setTurn_around() {
            mDance.turn_around();
            return mDance;
        }
    }
    

    说明:建造者模式通常用Builder进行链式调用(为了呈现链式调用这里修改下方法名),它的关键点在于每个setter方法都返回自身,代码我们可以这样写:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Dance dance = new DanceBuilder().setTurn_around().setFoot().setHand().create();
            Log.i("fukq","跳舞的顺序是:"+ ((Hiphop)dance).getPerformList());
        }
    }
    

    说明:通过链式调用,随意对顺序进行编辑,得到复杂对象Dance。

    5. 场景二实现示例:

    以AlertDialog为例:

               new AlertDialog.Builder(this)
                    .setIcon(R.mipmap.ic_launcher)
                    .setTitle("Title")
                    .setMessage("Messages")
                    .setPositiveButton("Button01",new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
    
                        }
                    }).setNegativeButton("Button02",new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
    
                        }
                    }).setNeutralButton("Button03", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
    
                        }
                    }).create().show();
    

    说明:通过类名Builder以及链式调用我们可以看出来它是一个建造者模式,通过Builder来组装Dialog的各个部分,我们分析下源码,再次验证下:

    public class AlertDialog extends AppCompatDialog implements DialogInterface {
        final AlertController mAlert;  // 1
        ...
        protected AlertDialog(@NonNull Context context) {
            this(context, 0);
        }
    
        protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) { // 2
            super(context, resolveDialogTheme(context, themeResId));
            this.mAlert = new AlertController(this.getContext(), this, this.getWindow());
        }
    
        protected AlertDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
            this(context, 0);
            this.setCancelable(cancelable);
            this.setOnCancelListener(cancelListener);
        }
        ...
        public void setTitle(CharSequence title) {
            super.setTitle(title);
            this.mAlert.setTitle(title);
        }
    
        public void setCustomTitle(View customTitleView) {
            this.mAlert.setCustomTitle(customTitleView);
        }
    
        public void setMessage(CharSequence message) {
            this.mAlert.setMessage(message);
        }
       ...
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            this.mAlert.installContent();
        }
       ...
        public static class Builder { // 3
            private final AlertParams P;
            private final int mTheme;
    
            public Builder(@NonNull Context context) {
                this(context, AlertDialog.resolveDialogTheme(context, 0));
            }
    
            public Builder(@NonNull Context context, @StyleRes int themeResId) {
                this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId)));
                this.mTheme = themeResId;
            }
    
            @NonNull
            public Context getContext() {
                return this.P.mContext;
            }
    
            public AlertDialog.Builder setTitle(@StringRes int titleId) {
                this.P.mTitle = this.P.mContext.getText(titleId);
                return this;
            }
    
            public AlertDialog.Builder setTitle(@Nullable CharSequence title) {
                this.P.mTitle = title;
                return this;
            }
    
            public AlertDialog.Builder setCustomTitle(@Nullable View customTitleView) {
                this.P.mCustomTitleView = customTitleView;
                return this;
            }
    
            public AlertDialog.Builder setMessage(@StringRes int messageId) {
                this.P.mMessage = this.P.mContext.getText(messageId);
                return this;
            }
    
            public AlertDialog.Builder setMessage(@Nullable CharSequence message) {
                this.P.mMessage = message;
                return this;
            }
    
           ...
            public AlertDialog.Builder setView(View view) {
                this.P.mView = view;
                this.P.mViewLayoutResId = 0;
                this.P.mViewSpacingSpecified = false;
                return this;
            }
            ...
            public AlertDialog create() { // 4
                AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
                this.P.apply(dialog.mAlert);
                dialog.setCancelable(this.P.mCancelable);
                if (this.P.mCancelable) {
                    dialog.setCanceledOnTouchOutside(true);
                }
    
                dialog.setOnCancelListener(this.P.mOnCancelListener);
                dialog.setOnDismissListener(this.P.mOnDismissListener);
                if (this.P.mOnKeyListener != null) {
                    dialog.setOnKeyListener(this.P.mOnKeyListener);
                }
    
                return dialog;
            }
    
            public AlertDialog show() {
                AlertDialog dialog = this.create();
                dialog.show();
                return dialog;
            }
        }
    }
    

    说明:
    ① 省略号的地方省略一些代码。
    ② 整个的AlertDialog大分为构造方法、set方法和内部类Builder。
    ③ 分析源码的顺序是:从调用开始的地方,查看涉及到的所有代码的源码,再来看下调用的代码:

     new AlertDialog.Builder(this)
                    .setTitle("Title")
                    ...
                    .create().show();
    

    所以这里查看源码的顺序是:先看AlertDialog的内部类Builder,然后再看setTitle方法,再看create方法,最后看show方法。
    ④ .Builder(this) 的源码:

            public Builder(@NonNull Context context) {
                this(context, AlertDialog.resolveDialogTheme(context, 0));
            }
    

    说明:这里只是传入了上下文this。
    ⑤ .setTitle("Title")的源码:

    private final AlertParams P;
    
            public AlertDialog.Builder setTitle(@Nullable CharSequence title) {
                this.P.mTitle = title;
                return this;
            }
    

    说明:给Title赋值,职责赋值给了AlertParams P中的title。
    ⑥ .create()的源码:

            public AlertDialog create() {
                AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
                this.P.apply(dialog.mAlert);
                dialog.setCancelable(this.P.mCancelable);
                if (this.P.mCancelable) {
                    dialog.setCanceledOnTouchOutside(true);
                }
    
                dialog.setOnCancelListener(this.P.mOnCancelListener);
                dialog.setOnDismissListener(this.P.mOnDismissListener);
                if (this.P.mOnKeyListener != null) {
                    dialog.setOnKeyListener(this.P.mOnKeyListener);
                }
    
                return dialog;
            }
    

    说明:这些代码里面,最可能和title相关的只剩下 this.P.apply(dialog.mAlert); 这一句,所以我们看下这个apply方法的源码:

     public void apply(AlertController dialog) {
                if (this.mCustomTitleView != null) {
                    dialog.setCustomTitle(this.mCustomTitleView);
                } else {
                    if (this.mTitle != null) {
                        dialog.setTitle(this.mTitle);
                    }
                    ...
                }
                ...
            }
    

    说明:apply这个方法是在AlertParams类中,dialog.mAlert 指的就是AlertController,所以apply的作用就是把AlertParams P中的字段值都赋值给AlertController的字段值。
    ⑦ show()方法从源码来看都是关于如何把Dialog显示在屏幕上的内容,不再附代码,不再分析。
    ⑧ 通过以上分析我们可以看到,当需要多个参数的时候,可以通过builder模式来传递需要的参数,而且兼容多种参数配置,就像kotlin中的默认参数所带来的便利一样。

    6. 后续

    如果大家喜欢这篇文章,欢迎点赞!
    如果想看更多 设计模式 方面的技术,欢迎关注!

    相关文章

      网友评论

          本文标题:通过示例和源码阐述建造者模式

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