一、什么是建造者模式
建造者模式(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适用于以下场景:
- 需要创建的对象比较复杂,有复杂的内部结构或者依赖关系。
- 需要创建的对象的属性比较少,但是大部分都是必选的或者没有默认值。
- 需要创建的对象需要保证不可变性或者线程安全性。
##六、与其他相似设计模式对比
设计模式 | 相似之处 | 不同之处 | 使用场景 |
---|---|---|---|
建造者模式 | 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 | 建造者模式有指导者这个角色,直接返回一个组装好的复杂产品,而其他创建型模式返回的是单一或相关的产品,建造者模式更关注产品的构造过程和表示,而其他创建型模式更关注产品的创建 | 建造者模式适用于创建复杂对象的情况,特别是当对象的构造过程涉及到多个步骤或者条件判断时 |
工厂方法模式 | 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 | 工厂方法模式只关注对象的创建,而建造者模式关注对象的构造过程和表示,工厂方法模式只有一个抽象产品类,而建造者模式有多个部件类 | 工厂方法模式适用于创建单一产品的情况,建造者模式适用于创建复杂产品的情况 |
抽象工厂模式 | 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 | 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族,而建造者模式返回一个组装好的复杂产品,抽象工厂模式没有指导者这个角色 | 抽象工厂模式适用于创建多个产品族的情况,建造者模式适用于创建复杂产品的情况 |
原型模式 | 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 | 原型模式通过复制一个现有的对象生成新对象,而建造者模式通过调用不同的方法生成新对象,原型模式不需要指导者和抽象建造者这两个角色 | 原型模式适用于创建重复或相似的对象的情况,建造者模式适用于创建复杂对象的情况 |