建造者模式

一、什么是建造者模式

建造者模式(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适用于以下场景:

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

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

设计模式 相似之处 不同之处 使用场景
建造者模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 建造者模式有指导者这个角色,直接返回一个组装好的复杂产品,而其他创建型模式返回的是单一或相关的产品,建造者模式更关注产品的构造过程和表示,而其他创建型模式更关注产品的创建 建造者模式适用于创建复杂对象的情况,特别是当对象的构造过程涉及到多个步骤或者条件判断时
工厂方法模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 工厂方法模式只关注对象的创建,而建造者模式关注对象的构造过程和表示,工厂方法模式只有一个抽象产品类,而建造者模式有多个部件类 工厂方法模式适用于创建单一产品的情况,建造者模式适用于创建复杂产品的情况
抽象工厂模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族,而建造者模式返回一个组装好的复杂产品,抽象工厂模式没有指导者这个角色 抽象工厂模式适用于创建多个产品族的情况,建造者模式适用于创建复杂产品的情况
原型模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 原型模式通过复制一个现有的对象生成新对象,而建造者模式通过调用不同的方法生成新对象,原型模式不需要指导者和抽象建造者这两个角色 原型模式适用于创建重复或相似的对象的情况,建造者模式适用于创建复杂对象的情况
相关推荐
电子科技圈2 小时前
先进封装、芯粒架构和3D集成——先进异构集成亟需兼具标准化与定制化能力的互联及总线IP解决方案
tcp/ip·设计模式·架构·软件构建·代码规范·设计规范
zjun10014 小时前
C++:2.工厂模式
设计模式
触底反弹5 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
ZZH_AI项目交付1 天前
同一套前摄心率算法,为什么 iPhone 16 Pro 稳,iPhone XR 会失准?
ios·app·ai编程
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范