建造者模式

一、什么是建造者模式

建造者模式(Builder Pattern)是一种创建型设计模式,它可以将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的展示。建造者模式可以让用户在不知道内部构建细节的情况下,更精细地控制对象的构造流程。

建造者模式的主要角色有:

  • 产品(Product):要创建的复杂对象,通常由多个部件组成。
  • 抽象建造者(Builder):定义了产品的创建过程和各个部件的构造方法,一般是一个接口或抽象类。
  • 具体建造者(Concrete Builder):实现了抽象建造者的接口,提供具体的构造方法和返回产品的方法。
  • 指挥者(Director):负责调用合适的建造者来组合产品,一般只有一个实例。

二、安卓源码中的实例

安卓开发中经常使用到建造者模式,例如AlertDialog、Notification、StringBuilder等类都是采用了建造者模式来创建对象。下面以AlertDialog为例,分析其源码实现。

AlertDialog是一个常用的对话框类,它可以显示标题、消息、图标、按钮等组件,也可以自定义布局。AlertDialog的构造方法是私有的,不能直接创建对象,而是通过Builder类来构建。Builder类是AlertDialog的一个静态内部类,它实现了抽象建造者的角色,提供了各种设置对话框属性的方法,例如setTitle、setMessage、setIcon、setPositiveButton等。这些方法都返回Builder对象本身,实现了链式调用。Builder类还提供了一个create方法,用于返回一个AlertDialog对象。这个方法会调用AlertDialog的私有构造方法,并将Builder对象作为参数传入。在AlertDialog的构造方法中,会根据Builder对象的属性来设置对话框的各个组件,并创建一个AlertController对象来管理对话框的显示和交互。AlertController类相当于指挥者的角色,它负责调用合适的Builder方法来组合对话框。

下面是AlertDialog和Builder类的部分源码:

java 复制代码
public class AlertDialog extends Dialog implements DialogInterface {
    // 省略其他代码

    // 私有构造方法
    protected AlertDialog(Context context, int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }

    // 静态内部类Builder
    public static class Builder {
        // 产品对象
        private final AlertController.AlertParams P;
        // 主题资源ID
        private int mTheme;

        // 构造方法
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }

        // 构造方法
        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }

        // 设置标题
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

        // 设置标题
        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, 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类创建AlertDialog对象的示例代码如下:

java 复制代码
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
       .setMessage("确定要退出吗?")
       .setIcon(R.drawable.ic_launcher)
       .setPositiveButton("确定", new DialogInterface.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
               finish();
           }
       })
       .setNegativeButton("取消", null)
       .create()
       .show();

从上面的代码可以看出,使用建造者模式可以让用户更灵活地构建对话框对象,而不需要关心对话框的内部实现细节。

三、Kotlin实现建造者模式

下面以一个电脑类为例,举例如何用Kotlin实现建造者模式。

我们要根据不同的配置来构建一个电脑类的实例,希望可以自由配置电脑CPU、RAM、显示器、键盘以及USB端口,从而组装出不同的Computer实例。如果使用Java来实现的话,最好的选择就是使用构建者模式。但是如果使用Kotlin来实现的话,有一个更简单的方法,那就是使用命名参数默认参数

命名参数是指在调用函数时,可以通过参数名来指定参数值,而不需要按照参数顺序来传递。默认参数是指在定义函数时,可以给参数指定一个默认值,这样在调用函数时,如果没有传递该参数,就会使用默认值。利用这两个特性,我们可以直接在电脑类的主构造函数中定义所有的属性,并给它们赋予默认值。然后在创建电脑对象时,只需要传递需要修改的属性即可。这样就避免了定义一个额外的Builder类,并且代码更加简洁。

下面是电脑类的Kotlin实现:

kotlin 复制代码
class Computer(
    val cpu: String = "Intel Core i5",
    val ram: Int = 8,
    val monitor: String = "Dell 24寸",
    val keyboard: String = "罗技无线键盘",
    val usb: Int = 4
) {
    override fun toString(): String {
        return "Computer(cpu='$cpu', ram=$ram, monitor='$monitor', keyboard='$keyboard', usb=$usb)"
    }
}

创建电脑对象的示例代码如下:

kotlin 复制代码
val computer1 = Computer()
println(computer1)

val computer2 = Computer(cpu = "AMD Ryzen 7", ram = 16, monitor = "华硕 27```markdown
寸\") .build()
println(computer2)

val computer3 = Computer(keyboard = "雷柏机械键盘", usb = 6) .build()
println(computer3)

输出结果如下:

ini 复制代码
Computer(cpu='Intel Core i5', ram=8, monitor='Dell 24寸', keyboard='罗技无线键盘', usb=4)
Computer(cpu='AMD Ryzen 7', ram=16, monitor='华硕 27寸', keyboard='罗技无线键盘', usb=4)
Computer(cpu='Intel Core i5', ram=8, monitor='Dell 24寸', keyboard='雷柏机械键盘', usb=6)

从上面的代码可以看出,使用Kotlin的命名参数和默认参数,可以非常方便地实现建造者模式,而不需要定义额外的Builder类。当然,这种方法也有一些局限性,例如不能保证对象的不可变性,不能控制对象的创建顺序,不能实现复杂的构造逻辑等。因此,在实际开发中,需要根据具体的需求和场景来选择合适的方法。

四、Java实现建造者模式

下面以一个计算机类为例,举例如何用Java实现建造者模式。

我们要根据不同的配置来构建一个计算机类的实例,希望可以自由配置计算机CPU、RAM、硬盘、显卡、主板、电源等部件,从而组装出不同的Computer实例。如果使用Java的构造方法或者setter方法来实现的话,会有以下几个问题:

  • 如果使用构造方法,参数过多时会导致代码可读性和可维护性降低,而且无法灵活地指定某些可选参数。
  • 如果使用setter方法,对象会产生不一致的状态,而且对象是可变的,不利于保证线程安全性和封装性。

因此,使用建造者模式可以解决这些问题,让用户更方便地构建复杂对象。具体步骤如下:

  • 定义一个计算机类(Product),包含各种属性,并提供一个私有的构造方法和一个静态内部类Builder。
  • 定义一个Builder类(Concrete Builder),包含与Product类相同的属性,并提供一个公有的构造方法和各种设置属性的方法,这些方法都返回Builder对象本身,实现链式调用。Builder类还提供了一个build方法,用于返回一个Product对象。这个方法会调用Product类的私有构造方法,并将Builder对象作为参数传入。
  • 在Product类的私有构造方法中,根据Builder对象的属性来初始化自身的属性。

下面是计算机类和Builder类的部分源码:

java 复制代码
public class Computer {
    // 必选属性
    private String cpu;
    private String ram;
    // 可选属性
    private String hardDisk;
    private String keyboard;
    private String mouse;

    // 私有构造方法
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.hardDisk = builder.hardDisk;
        this.keyboard = builder.keyboard;
        this.mouse = builder.mouse;
    }

    // 静态内部类Builder
    public static class Builder {
        // 必选属性
        private String cpu;
        private String ram;
        // 可选属性
        private String hardDisk;
        private String keyboard;
        private String mouse;

        // 公有构造方法
        public Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        // 设置硬盘
        public Builder setHardDisk(String hardDisk) {
            this.hardDisk = hardDisk;
            return this;
        }

        // 设置键盘
        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        // 设置鼠标
        public Builder setMouse(String mouse) {
            this.mouse = mouse;
            return this;
        }

        // 返回产品对象
        public Computer build() {
            return new Computer(this);
        }
    }
}

创建计算机对象的示例代码如下:

java 复制代码
Computer computer1 = new Computer.Builder("Intel Core i7", 16).build();
System.out.println(computer1);

Computer computer2 = new Computer.Builder("AMD Ryzen 9", 32)
                    .setHardDisk("Samsung SSD 1TB")
                    .setKeyboard("Logitech G915")
                    .setMouse("Logitech G502")
                    .build();
System.out.println(computer2);

输出结果如下:

ini 复制代码
Computer{cpu='Intel Core i7', ram=16, hardDisk='null', keyboard='null', mouse='null'}
Computer{cpu='AMD Ryzen 9', ram=32, hardDisk='Samsung SSD 1TB', keyboard='Logitech G915', mouse='Logitech G502'}

从上面的代码可以看出,使用建造者模式可以让用户更清晰地构建复杂对象,而不需要关心对象的内部实现细节。同时,使用Builder类可以保证对象的不可变性和线程安全性,以及对象创建过程的顺序性和逻辑性。

五、优缺点和使用场景

语言 优点 缺点 使用场景
Kotlin 代码简洁,调用方便,实现灵活 对象可变,不能保证线程安全,不能控制创建顺序和逻辑 需要创建简单、灵活、可变的对象
Java 对象不可变,可以保证线程安全,可以控制创建顺序和逻辑 代码冗长,调用繁琐,实现固定 需要创建复杂、固定、不可变的对象

#####Kotlin的优点:

  • 代码更简洁,不需要定义额外的Builder类。
  • 调用更方便,可以通过命名参数来指定需要修改的属性,而不需要按照顺序来传递参数。
  • 实现更灵活,可以根据需要给属性赋予默认值,也可以在主构造函数中添加逻辑判断或者初始化代码。

#####Kotlin的缺点:

  • 对象是可变的,不能保证对象的不可变性和线程安全性。
  • 不能控制对象的创建顺序,如果对象的属性之间有依赖关系,可能会导致错误或异常。
  • 不能实现复杂的构造逻辑,如果对象的创建过程涉及到多个步骤或者条件判断,可能会导致代码冗长或者混乱。

#####Kotlin适用于以下场景:

  • 需要创建的对象比较简单,没有复杂的内部结构或者依赖关系。
  • 需要创建的对象的属性比较多,但是大部分都是可选的或者有默认值。
  • 需要创建的对象不需要保证不可变性或者线程安全性。

#####Java的优点:

  • 对象是不可变的,可以保证对象的不可变性和线程安全性。
  • 可以控制对象的创建顺序,如果对象的属性之间有依赖关系,可以通过Builder类来保证正确的构造过程。
  • 可以实现复杂的构造逻辑,如果对象的创建过程涉及到多个步骤或者条件判断,可以通过Builder类来封装和隐藏这些细节。

#####Java的缺点:

  • 代码更冗长,需要定义一个额外的Builder类来封装对象的构造过程。
  • 调用更繁琐,需要按照顺序来传递参数,而且不能通过参数名来指定参数值。
  • 实现更固定,不能根据需要给属性赋予默认值,也不能在主构造函数中添加逻辑判断或者初始化代码。

#####Java适用于以下场景:

  • 需要创建的对象比较复杂,有复杂的内部结构或者依赖关系。
  • 需要创建的对象的属性比较少,但是大部分都是必选的或者没有默认值。
  • 需要创建的对象需要保证不可变性或者线程安全性。

##六、与其他相似设计模式对比

设计模式 相似之处 不同之处 使用场景
建造者模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 建造者模式有指导者这个角色,直接返回一个组装好的复杂产品,而其他创建型模式返回的是单一或相关的产品,建造者模式更关注产品的构造过程和表示,而其他创建型模式更关注产品的创建 建造者模式适用于创建复杂对象的情况,特别是当对象的构造过程涉及到多个步骤或者条件判断时
工厂方法模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 工厂方法模式只关注对象的创建,而建造者模式关注对象的构造过程和表示,工厂方法模式只有一个抽象产品类,而建造者模式有多个部件类 工厂方法模式适用于创建单一产品的情况,建造者模式适用于创建复杂产品的情况
抽象工厂模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族,而建造者模式返回一个组装好的复杂产品,抽象工厂模式没有指导者这个角色 抽象工厂模式适用于创建多个产品族的情况,建造者模式适用于创建复杂产品的情况
原型模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 原型模式通过复制一个现有的对象生成新对象,而建造者模式通过调用不同的方法生成新对象,原型模式不需要指导者和抽象建造者这两个角色 原型模式适用于创建重复或相似的对象的情况,建造者模式适用于创建复杂对象的情况
相关推荐
越甲八千4 小时前
重温设计模式--享元模式
设计模式·享元模式
码农爱java5 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
越甲八千6 小时前
重温设计模式--中介者模式
windows·设计模式·中介者模式
犬余6 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
Theodore_10227 小时前
1 软件工程——概述
java·开发语言·算法·设计模式·java-ee·软件工程·个人开发
越甲八千9 小时前
重拾设计模式--组合模式
设计模式·组合模式
思忖小下12 小时前
梳理你的思路(从OOP到架构设计)_设计模式Composite模式
设计模式·组合模式·eit
机器视觉知识推荐、就业指导12 小时前
C++设计模式:组合模式(公司架构案例)
c++·后端·设计模式·组合模式
越甲八千12 小时前
重拾设计模式--工厂模式(简单、工厂、抽象)
c++·设计模式
重生之绝世牛码13 小时前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式