美文网首页Android技术知识Android开发Android开发经验谈
Android源码设计模式(三)-- 自由扩展你的项目:Buil

Android源码设计模式(三)-- 自由扩展你的项目:Buil

作者: 随时学丫 | 来源:发表于2018-07-11 16:10 被阅读38次

Android源码设计模式(一) -- 面向对象的六大原则
Android源码设计模式(二)-- 应用最广的模式:单例模式
Android源码设计模式(三)-- 自由扩展你的项目:Builder 模式
Android源码设计模式(四)-- 时势造英雄:策略模式
Android源码设计模式(五)-- 使编程更有灵活性:责任链模式
Android源码设计模式(六)— 编程好帮手:代理模式
Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者模式

简书 MD 语法不识别 [TOC] ,也不会根据标题行(#) 来插入目录,作为每次看资料喜欢先看目录把握总体的我来说,很不习惯,查找跳转也不方便,所以,如果看到文章没有目录预览的,请安装脚本:简书目录脚本地址

一、Builder 模式的定义

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

二、Builder 模式的使用场景

  1. 相同的方法,不同的执行顺序,产生不同的事件结果时;
  2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;
  3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适;
  4. 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时。

三、Builder 模式的 UML 类图

builder-uml.png

角色介绍

  • Product 产品类 : 产品的抽象类。
  • Builder:抽象 Builder 类, 规范产品的组建,一般是由子类实现具体的组件过程。
  • ConcreteBuilder : 具体的 Builder 类。
  • Director : 统一组装过程(可省略)。

四、Builder 模式的简单实现

简单实现的介绍

电脑的组装过程较为复杂,步骤繁多,但是顺序却是不固定的,为了易于理解,我们把计算机组装过程简化为构建主机,设置操作系统,设置显示器 3 个部分,然后通过 Director 和具体的 Builder 来构建计算机对象。下面我们以组装电脑为例来演示一下简单且经典的builder 模式。

package com.dp.example.builder;
/**
 * Computer产品抽象类, 为了例子简单, 只列出这几个属性
 * @author mrsimple
 */
public abstract class Computer {

    protected int mCpuCore = 1;
    protected int mRamSize = 0;
    protected String mOs = "Dos";

    protected Computer() {

    }
    // 设置CPU核心数
    public abstract void setCPU(int core);
    // 设置内存
    public abstract void setRAM(int gb);
    // 设置操作系统
    public abstract void setOs(String os);
    @Override
    public String toString() {
        return "Computer [mCpuCore=" + mCpuCore + ", mRamSize=" + mRamSize
                + ", mOs=" + mOs + "]";
    }
}

package com.dp.example.builder;
/**
 * Apple电脑
 */
public class AppleComputer extends Computer {

    protected AppleComputer() {
    }
    @Override
    public void setCPU(int core) {
        mCpuCore = core;
    }
    @Override
    public void setRAM(int gb) {
        mRamSize = gb;
    }
    @Override
    public void setOs(String os) {
        mOs = os;
    }
}

package com.dp.example.builder;
/**
 * builder抽象类
 *
 */
public abstract class Builder {
    // 设置CPU核心数
    public abstract void buildCPU(int core);
    // 设置内存
    public abstract void buildRAM(int gb);
    // 设置操作系统
    public abstract void buildOs(String os);
    // 创建Computer
    public abstract Computer create();
}

package com.dp.example.builder;
public class ApplePCBuilder extends Builder {
    private Computer mApplePc = new AppleComputer();
    @Override
    public void buildCPU(int core) {
        mApplePc.setCPU(core);
    }
    @Override
    public void buildRAM(int gb) {
        mApplePc.setRAM(gb);
    }
    @Override
    public void buildOs(String os) {
        mApplePc.setOs(os);
    }
    @Override
    public Computer create() {
        return mApplePc;
    }
}

package com.dp.example.builder;
public class Director {
    Builder mBuilder = null;
    /**
     * @param builder
     */
    public Director(Builder builder) {
        mBuilder = builder;
    }
    /**
     * 构建对象
     * @param cpu
     * @param ram
     * @param os
     */
    public void construct(int cpu, int ram, String os) {
        mBuilder.buildCPU(cpu);
        mBuilder.buildRAM(ram);
        mBuilder.buildOs(os);
    }
}

/**
 * 经典实现较为繁琐
 * @author mrsimple
 */
public class Test {
    public static void main(String[] args) {
        // 构建器
        Builder builder = new ApplePCBuilder();
        // Director
        Director pcDirector = new Director(builder);
        // 封装构建过程, 4核, 内存2GB, Mac系统
        pcDirector.construct(4, 2, "Mac OS X 10.9.1");
        // 构建电脑, 输出相关信息
        System.out.println("Computer Info : " + builder.create().toString());
    }
}

通过 Builder 来构建产品对象, 而 Director 封装了构建复杂产品对象对象的过程,不对外隐藏构建细节。Builder 和 Director 一起将复杂对象的构建和表示分离,使得同样的构建过程可以创建不同的对象。

Director 在实际开发中经常被省略,而是直接用 Builder 来进行对象的组装,这个 Builder 通常为链式调用,它的关键点是每个 setter 方法都返回自身,也就是 return this,这样就使得 setter 方法可以链式调用。

new Builder().setA("A").setB("B").build();

通过这种方式不仅去除了 Director 角色,整个结构更加简单,也能对 Product 对象的组装过程有更加精细的控制。

省去 Director 角色的 Builder,我之前写过一篇 PopupWindow 的封装,就是采用 Builder 模式。MPopupWindow

五、 Android 源码中的 Builder 模式实现

在 Android 源码中,我们最常用到的 Builder 模式就是 AlertDialog.Builder, 使用该 Builder 来构建复杂的AlertDialog 对象。简单示例如下 :

    //显示基本的AlertDialog  
    private void showDialog(Context context) {  
        AlertDialog.Builder builder = new AlertDialog.Builder(context);  
        builder.setIcon(R.drawable.icon);  
        builder.setTitle("Title");  
        builder.setMessage("Message");  
        builder.setPositiveButton("Button1",  
                new DialogInterface.OnClickListener() {  
                    public void onClick(DialogInterface dialog, int whichButton) {  
                        setTitle("点击了对话框上的Button1");  
                    }  
                });  
        builder.setNeutralButton("Button2",  
                new DialogInterface.OnClickListener() {  
                    public void onClick(DialogInterface dialog, int whichButton) {  
                        setTitle("点击了对话框上的Button2");  
                    }  
                });  
        builder.setNegativeButton("Button3",  
                new DialogInterface.OnClickListener() {  
                    public void onClick(DialogInterface dialog, int whichButton) {  
                        setTitle("点击了对话框上的Button3");  
                    }  
                });  
        builder.create().show();  // 构建AlertDialog, 并且显示
    } 
result.png

下面我们看看 AlertDialog 的相关源码 :

// AlertDialog
public class AlertDialog extends Dialog implements DialogInterface {
    // Controller, 接受Builder成员变量P中的各个参数
    private AlertController mAlert;

    // 构造函数
    protected AlertDialog(Context context, int theme) {
        this(context, theme, true);
    }

    // 4 : 构造AlertDialog
    AlertDialog(Context context, int theme, boolean createContextWrapper) {
        super(context, resolveDialogTheme(context, theme), createContextWrapper);
        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }

    // 实际上调用的是mAlert的setTitle方法
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }

    // 实际上调用的是mAlert的setCustomTitle方法
    public void setCustomTitle(View customTitleView) {
        mAlert.setCustomTitle(customTitleView);
    }
    
    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }

    // AlertDialog其他的代码省略
    
    // ************  Builder为AlertDialog的内部类   *******************
    public static class Builder {
        // 1 : 存储AlertDialog的各个参数, 例如title, message, icon等.
        private final AlertController.AlertParams P;
        // 属性省略
        
        /**
         * Constructor using a context for this builder and the {@link AlertDialog} it creates.
         */
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }


        public Builder(Context context, int theme) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, theme)));
            mTheme = theme;
        }
        
        // Builder的其他代码省略 ......

        // 2 : 设置各种参数
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }
        
        
        public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

        public Builder setIcon(int iconId) {
            P.mIconId = iconId;
            return this;
        }
        
        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }
        
        
        public Builder setView(View view) {
            P.mView = view;
            P.mViewSpacingSpecified = false;
            return this;
        }
        
        // 3 : 构建AlertDialog, 传递参数
        public AlertDialog create() {
            // 调用new AlertDialog构造对象, 并且将参数传递个体AlertDialog 
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
            // 5 : 将P中的参数应用的dialog中的mAlert对象中
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }
    }
    
}

可以看到,通过 Builder 来设置 AlertDialog 中的 title, message, button 等参数, 这些参数都存储在类型为AlertController.AlertParams 的成员变量 P 中,AlertController.AlertParams 中包含了与之对应的成员变量。在调用 Builder 类的 create 函数时才创建 AlertDialog, 并且将 Builder 成员变量 P 中保存的参数应用到 AlertDialog的 mAlert 对象中,即 P.apply(dialog.mAlert) 代码段。我们看看 apply 函数的实现 :

        public void apply(AlertController dialog) {
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
                if (mTitle != null) {
                    dialog.setTitle(mTitle);
                }
                if (mIcon != null) {
                    dialog.setIcon(mIcon);
                }
                if (mIconId >= 0) {
                    dialog.setIcon(mIconId);
                }
                if (mIconAttrId > 0) {
                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
                }
            }
            if (mMessage != null) {
                dialog.setMessage(mMessage);
            }
            if (mPositiveButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                        mPositiveButtonListener, null);
            }
            if (mNegativeButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                        mNegativeButtonListener, null);
            }
            if (mNeutralButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                        mNeutralButtonListener, null);
            }
            if (mForceInverseBackground) {
                dialog.setInverseBackgroundForced(true);
            }
            // For a list, the client can either supply an array of items or an
            // adapter or a cursor
            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
                createListView(dialog);
            }
            if (mView != null) {
                if (mViewSpacingSpecified) {
                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                            mViewSpacingBottom);
                } else {
                    dialog.setView(mView);
                }
            }
        }

在 apply 函数中,只是将 AlertParams 参数设置到 AlertController 中。例如,将标题设置到 Dialog 对应的标题识图中,将 Message 设置到内容视图中等。当我们获取到 AlertDialog 对象后,通过 show 函数就可以显示这个对话框。 AlertDialog 中相关代码如下:

//显示Dialog
public void show() {
        //已经是显示状态,则return
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }
        mCanceled = false;
        //1、onCreate调用
        if (!mCreated) {
            dispatchOnCreate(null);
        }
        //2、onStart
        onStart();
        //3、获取DecorView
        mDecor = mWindow.getDecorView();
        //省略  
        //4、获取布局参数
        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }
        try {  
            //5、将mDecor添加到mWindowManager中
            mWindowManager.addView(mDecor, l);
            mShowing = true;
            //发送一个显示Dialog的消息
            sendShowMessage();
        } finally {
        }
    }

在 show 函数中主要做了如下几个事情:

  1. 通过 dispatchOnCreate 函数来调用 AlertDialog 中的 onCreate 函数。
  2. 调用 AlertDialog 的 onStart 函数。
  3. 最后将 Dialog 的 DecorView 添加到 WindowManager 中。

很明显,这就是一系列的典型的生命周期函数。按照惯例,AlertDialog 的内容视图构建按理应该在 onCreate 函数中,我们来看看是不是:

public class AlertDialog extends Dialog implements DialogInterface {
   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //调用了AlertController的installContent方法
        mAlert.installContent();
    }
}

在 onCreate 函数中主要调用了 AlertController 的 installContent 方法,Dialog 中的 onCreate 函数只是一个空实现而已,可以忽略他。那么 AlertDialog 的内容视图必然就在 installContent 函数中。

public void installContent() {
        /* We use a custom title so never request a window title */
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
        setupDecor();
    }
    private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        // TODO: use layout hint side for long messages/lists
        return mAlertDialogLayout;
    }

installContent 函数的代码很少,但极为重要,它调用了 Window 对象的 setContentView,这个 setContentView 就与 Activity 中的一模一样,实际上 Activity 最终也是调用 Window 对象的 setContentView 函数。因此,这里就是设置 AlertDialog 的内容布局,这个布局就是 mAlertDialogLayout 的值。这个值在AlertController 的构造函数中进行了初始化,具体代码如下:

public AlertController(Context context, DialogInterface di, Window window) {
        mContext = context;
        mDialogInterface = di;
        mWindow = window;
        mHandler = new ButtonHandler(di);
        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
        //AlertController的布局id,也就是alert_dialog.xml布局
        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        mListLayout = a.getResourceId(
                R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
        //省略
        a.recycle();
}

从 AlertController 的构造函数中可以看到,AlertDialog 的布局资源就是 select_dialog.xml 这个布局,我们直接大致看他的结构如下:

AlertController 结构

当通过 Builder 对象的 setTitle、setMessage 等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog 也允许你通过 setView 传入内容视图,这个内容视图就是替换掉图中的内容视图(蓝色区域),AlertDialog 预留了一个 costomPanel 区域用来显示用户自定义的内容视图。我们来看看 setupView 函数:

private void setupView() {
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        //1、获取内容区域
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        //1、获取按钮
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
        // Install custom content before setting up the title or buttons so
        // that we can handle panel overrides.
        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        //自定义内容视图区域
        setupCustomContent(customPanel);
        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
        // Resolve the correct panels and remove the defaults, if needed.
        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
        //2、初始化内容
        setupContent(contentPanel);
        setupButtons(buttonPanel);
        //2、初始化标题
        setupTitle(topPanel);
        //自定义视图可见性
        final boolean hasCustomPanel = customPanel != null
                && customPanel.getVisibility() != View.GONE;
        final boolean hasTopPanel = topPanel != null
                && topPanel.getVisibility() != View.GONE;
        final boolean hasButtonPanel = buttonPanel != null
                && buttonPanel.getVisibility() != View.GONE;
        // Only display the text spacer if we don't have buttons.
        if (!hasButtonPanel) {
            if (contentPanel != null) {
                final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
                if (spacer != null) {
                    spacer.setVisibility(View.VISIBLE);
                }
            }
            mWindow.setCloseOnTouchOutsideIfNotSet(true);
        }
        if (hasTopPanel) {
            // Only clip scrolling content to padding if we have a title.
            if (mScrollView != null) {
                mScrollView.setClipToPadding(true);
            }
            // Only show the divider if we have a title.
            final View divider;
            if (mMessage != null || mListView != null || hasCustomPanel) {
                divider = topPanel.findViewById(R.id.titleDivider);
            } else {
                divider = topPanel.findViewById(R.id.titleDividerTop);
            }
            if (divider != null) {
                divider.setVisibility(View.VISIBLE);
            }
        }
        // Update scroll indicators as needed.
        if (!hasCustomPanel) {
            final View content = mListView != null ? mListView : mScrollView;
            if (content != null) {
                final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
                        | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
                content.setScrollIndicators(indicators,
                        View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
            }
        }
        final TypedArray a = mContext.obtainStyledAttributes(
                null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
        //设置背景
        setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
                hasTopPanel, hasCustomPanel, hasButtonPanel);
        a.recycle();
    }
    //自定义内容视图区域
    private void setupCustomContent(ViewGroup customPanel) {
        final View customView;
        //如果用户设置了内容视图,那么将它显示在customPanel的custom布局里面
        if (mView != null) {
            customView = mView;
        } else if (mViewLayoutResId != 0) {
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
        } else {
            customView = null;
        }
        final boolean hasCustomView = customView != null;
        if (!hasCustomView || !canTextInput(customView)) {
            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
        }
        if (hasCustomView) {
            final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
            //显示用户设置的视图
            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
            if (mViewSpacingSpecified) {
                custom.setPadding(
                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
            }
            if (mListView != null) {
                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
            }
        } else {
            customPanel.setVisibility(View.GONE);
        }
    }

这个 setupView 顾名思义就是初始化 AlertDialog 布局中的各个部分,如标题区域、按钮区域、内容区域等,在该函数调用之后整个 Dialog 的视图内容全部设置完毕。而这些各区域的视图都属于 mAlertDialogLayout 布局中的子元素,Window 对象又关联了 mAlertDialogLayout 的整个布局树,当调用完 setupView 之后整个视图树的数据都填充完毕。当用户调用了 show 函数时,WindosManager 会将 Window 对象的 DecorView (也就是 mAlertDialogLayout 对应的视图),添加到用户的窗口上,并且显示出来。至此,整个 Dialog 就会出现在用户的视野中了。

AlertDialog
  • Builder 内部类
  • onCreate() 方法调用 AlertController 类 installContent() 方法设置内容布局
AlertDialog.Builder --- Builder、ConcreteBuilder、Dirctor角色
  • 初始化 AlertController.AlertParams 对象 p 储存数据 (Title、message、icon 等)。

  • set 方法将数据储存至 AlertController.AlertParams 对象 p。

  • create() 方法储存将 AlertController.AlertParams 对象 p 数据应用至 AlertController 对象 dialog.mAlert。

  • show() 方法调用 AlertDialog 父类 Dialog 的 show() 方法加载布局。

AlertController
  • AlertParams内部类
  • 接收数据(Title、message、icon等)
  • installContent()方法调用Window对象的setContentView函数设置AlertDialog的内容布局,并调用setupView()方法初始化布局
  • setupView()初始化布局
AlertController.AlertParams
  • 用于储存数据(Title、message、icon等)
  • apply()方法将数据传递给AlertDialog类实例化的AlertController对象

在 AlertDialog 的 Builder 模式中并没有看到 Director 角色的出现(其实在很多场景中,Android 并没有按照《设计模式: 可复用面向对象软件的基础》一书中描述的经典模式来实现,而是做了些修改,使得更易于使用)。这里的AlertDialog.Builder 同时扮演了 builder、ConcreteBuilder、Dirctor 的角色,简化了 Builder 模式的设计。

六、总结

优点与缺点

优点

  • 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
  • 建造者独立,容易扩展;
  • 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。

缺点

  • 会产生多余的 Builder 对象以及 Director 对象,消耗内存;
  • 对象的构建过程暴露。

相关文章

网友评论

    本文标题:Android源码设计模式(三)-- 自由扩展你的项目:Buil

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