建造者模式(Builder Pattern)

什么是建造者模式

定义

将一个复杂对象的构建与它的表示分离,使得一样的构造过程能够建立不一样的表示。java

即逐步创建由多个部件组成的对象,每次创建中各部件对外接口一致,但内部实现功能能够不同,相同的构建过程能够建立不一样的对象。android

特色

适用于流程固定(顺序不必定固定),但建造的目标不一样的场景。例如购买电脑,不一样人对电脑有不一样的需求,办公向的,游戏向的。但都是由机箱、主板、显卡、电源等组成。又如建造房子,不一样房子结构不一样,但都有地基、墙、地板、门窗等等。又如软件开发,产品经理提出功能需求,技术领导拆分任务,指定具体程序员完成。程序员

适用范例

适用于构建许多字段和嵌套对象组合成的复杂对象。安全

Lots of subclasses create another problem

例如,假设要建造上图中的 House 。建简单的房子,只须要建造墙和地板,安装门窗,并建造一个屋顶。 可是,若是你想要建造一个更大,更明亮的房子,后院和其余设施(如加热系统,管道和电线)怎么办?app

最简单的解决方案是扩展基类 House 并建立一组子类来覆盖参数的全部组合。但它会产生不少的子类。 任何新参数(例如门廊样式)都须要增长新的子类。ide

还有另外一种方法不涉及子类。在基类 House 中建立一个巨型构造函数,其中包含控制 house 对象的全部可能参数。这种方法虽然确实消除了对子类的需求,但它产生了另外一个问题。函数

The telescopic constructor

那就是产生了不少未使用的参数,令构造函数变得很难看。如不是全部房子都有雕像和泳池。ui

建造者模式将对象的构造过程拆分,并由专门的 Builder 对象来构造对象。this

Applying the Builder pattern

该模式将对象构造过程拆分红一组步骤(buildWalls,buildDoor等)。要建立对象,须要在 Builder 对象中执行相应的步骤,并只调用须要的步骤便可,不须要调用全部步骤。spa

当须要构建产品的各类表示时,某些建造步骤可能须要不一样的实现。 例如,小屋的墙壁能够用木头建造,但城堡的墙壁必须用石头建造。

在这种状况下,能够建立多个不一样的 Builder 实例类,这些 Builder 以不一样的方式实现同一组构建步骤。 而后,能够在构造过程当中使用这些 Builder 来生成不一样类型的对象。

img

例如,假设一个 Builder ,用木头和玻璃建造一切,第二个用石头和铁建造一切,第三个用金和钻石制造一切。 经过调用相同的步骤,能够得到第一个建筑师的常规房屋,第二个建筑物的小城堡和第三个建筑物的宫殿。

为了更好的构建复杂对象,也能够建立 Director 类,将一系列建造步骤封装到其中,经过调用它来按顺序执行一系列建造步骤。它并非必须的,可是将经常使用的建造步骤封装能够更好在其余地方复用。

结构

角色 类别 说明
Builder 抽象的建造者 接口或抽象类,将建造的具体过程交由其子类实现,便于扩展。但全部建造步骤和返回产品函数均在此声明。
ConcreteBuilder 具体的建造者 能够有多个,实现 Builder 中的全部建造步骤。不一样建造者的实现方式能够不一样。
Product 具体的产品类 要被建造的较为复杂的对象。
Director 导演者 定义调用建造步骤的顺序。通常不与产品类发生依赖关系,与建造者类直接交互,一般封装可能被常用的一系列建造步骤。

UML图

Structure of the Builder design pattern

在整个过程当中:

  1. 用户不需了解建造过程和细节
  2. 用户只需给出复杂对象的的类型便可建立对象
  3. 建造者按流程一步步建立出复杂对象

示例

完整版 - SettingItemView

settingItemViewComponent

如上图所示,设置选项由不少个部分组成,但每次实际使用时,不须要同时启用全部部分,而是根据使用状况不一样,组合不一样部分,同时一些部分还有前后顺序,如点击事件需先添加了右箭头才能添加。此类复杂对象可使用建造者模式建立(此处仅用于演示建造者模式。 android 中实际使用时,为其添加自定义 attr 属性,在 xml 中直接设置更方便,而且实际应用建造模式时并不须要严格建立四个部分,除 Product 外其他三个部分常常混合使用)。

  1. Product — SettingItemView

    class SettingItemView(context: Context) : LinearLayout(context) {
    		// ... 省略构建函数
        fun addTitle(title: String) {
            settingTitleView.visibility = View.VISIBLE
            settingTitleView.text = title
        }
    
        fun addRightArrow(isShow: Boolean) {
            settingRightArrow.visibility = View.VISIBLE
            settingRightArrow.visibility = if (isShow) View.VISIBLE else View.GONE
        }
    
        fun addOnClickListener(listener: View.OnClickListener) {
            if (if (settingRightArrow.visibility != View.VISIBLE) {
                throw IllegalStateException("add click listener should after adding right arrow")
            }
            setOnClickListener(listener)
        }
      	// ... 省略设置函数
    }
    复制代码
  2. Builder

    interface Builder {
      
        fun reset()
    
        fun addTitle(title: String)
    
        fun addRightArrow(isShow: Boolean)
    
        fun addOnClickListener(listener: View.OnClickListener)
    
        fun create(): SettingItemView
    }
    复制代码
  3. ConcreteBuilder

    class ConcreteBuilder(private val context: Context) : Builder {
    
        private var settingItemView = SettingItemView(context)
      
        override fun reset() {
            settingItemView = SettingItemView(context)
        }
    
        override fun addTitle(title: String) {
            settingItemView.addTitle(title)
        }
    
        override fun addRightArrow(isShow: Boolean) {
            settingItemView.addRightArrow(isShow)
        }
    
        override fun addOnClickListener(listener: View.OnClickListener) {
            settingItemView.addOnClickListener(listener)
        }
    
        override fun create(): SettingItemView {
            return settingItemView
        }
    }
    复制代码

    Builder 构建时另外一种经常使用方式是将全部参数记录下来,在建立时再统一构建并检查错误。

  4. Director

    class Director(private val builder: Builder) {
    
        fun construct() {
            builder.addTitle("帐号与安全")
            builder.addRightArrow(true)
            builder.addOnClickListener(View.OnClickListener {
                Toast.makeText(it.context, "点击", Toast.LENGTH_SHORT).show()
            })
        }
    }
    复制代码

    Director 一般只负责调用 Buidler 执行操做,不直接返回产品。

  5. 调用

    val builder = ConcreteBuilder(context)
    val director = Director(builder)
    val settingItemView = builder.create()
    复制代码

将生成的 SettingItemView 对象加载到窗口中去后以下图所示:

settingItemView.png

简化版 - AlertDialog

在实际应用中,采用建造模式构建复杂对象时,一般会对建造模式进行必定简化,大体简化以下:

  1. 直接使用建造者模式建立对象,因此不用再定义一个抽象建造类接口,直接提供一个具体的建造类便可。
  2. 建立一个复杂对象时,可能有不少种不一样的选择和步骤,能够去掉导演者,把导演者的功能和客户的功能结合起来,此时客户就至关于导演者,它来指导建造器去构建须要的复杂对象。
public class AlertDialog extends Dialog implements DialogInterface {
    // ... 省略若干构造方法
    protected AlertDialog(Context context, @StyleRes int themeResId) {
        this(context, themeResId, true);
    }
    // ... 省略若干设置属性方法
    public void setView(View view) {
        mAlert.setView(view);
    }
    // 建造者
    public static class Builder {
        // ... 省略
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }
        // 建造者中提供建立目标对象的方法
        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            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;
        }
    }
}
复制代码

使用建造模式:

val builder = AlertDialog.Builder(context)
builder.setTitle("问题:")
    .setMessage("请问你满十八岁了吗?")
    .setIcon(R.mipmap.ic_launcher_round)
    .setCancelable(true)
val dialog = builder.create()
复制代码

AlertDialog 的建造者模式 AlertDialog.Builder 同时扮演了 BuilderConcreateBuilderDirector 三个角色。

优缺点

优势

  1. 建立产品的步骤和产品自己分离,相同中的建立过程能够建立出不一样的产品。
  2. 容许对象经过多个步骤建立,能够改变过程。
  3. 向客户隐藏具体实现。
  4. 建造者相对独立,能够方便的替换或增长新的。

缺点

  1. 产品必须有共同点,范围有限制。
  2. 若是产品内部发生改变,多个建造者都须要修改,成本大。
  3. 采用生成器模式建立对象的客户,须要具有更多的领域知识。

使用场景

  1. 对象具备复杂的内部结构
  2. 想将复杂对象的建立和使用分离
  3. 要生产的对象属性相互依赖,须要指定生成顺序
  4. 一些基本部分不会变,而其组合常常变化的时候

建造者模式 VS 工厂模式

类似

都属于建立模式

不一样

  1. 意图不一样

    工厂模式关注的是对象总体,不关注对象的组成部分,建造者模式关注对象组成部分的建立过程。

  2. 粒度不一样

    工厂模式建立的产品性质相对单一,建造者模式建立的是复合产品,由复杂部分组成,部分不一样构成的产品也不一样。

Article by Wuhb

相关文章
相关标签/搜索