美文网首页
建造者模式

建造者模式

作者: 长点点 | 来源:发表于2023-04-25 16:19 被阅读0次

    一、什么是建造者模式

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

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

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

    二、安卓源码中的实例

    安卓开发中经常使用到建造者模式,例如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类的部分源码:

    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对象的示例代码如下:

    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实现:

    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)"
        }
    }
    

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

    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)
    

    输出结果如下:

    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类的部分源码:

    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);
            }
        }
    }
    

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

    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);
    

    输出结果如下:

    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适用于以下场景:
    • 需要创建的对象比较复杂,有复杂的内部结构或者依赖关系。
    • 需要创建的对象的属性比较少,但是大部分都是必选的或者没有默认值。
    • 需要创建的对象需要保证不可变性或者线程安全性。

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

    设计模式 相似之处 不同之处 使用场景
    建造者模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 建造者模式有指导者这个角色,直接返回一个组装好的复杂产品,而其他创建型模式返回的是单一或相关的产品,建造者模式更关注产品的构造过程和表示,而其他创建型模式更关注产品的创建 建造者模式适用于创建复杂对象的情况,特别是当对象的构造过程涉及到多个步骤或者条件判断时
    工厂方法模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 工厂方法模式只关注对象的创建,而建造者模式关注对象的构造过程和表示,工厂方法模式只有一个抽象产品类,而建造者模式有多个部件类 工厂方法模式适用于创建单一产品的情况,建造者模式适用于创建复杂产品的情况
    抽象工厂模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族,而建造者模式返回一个组装好的复杂产品,抽象工厂模式没有指导者这个角色 抽象工厂模式适用于创建多个产品族的情况,建造者模式适用于创建复杂产品的情况
    原型模式 都是创建型模式,都可以将对象的创建过程封装起来,对客户端隐藏细节 原型模式通过复制一个现有的对象生成新对象,而建造者模式通过调用不同的方法生成新对象,原型模式不需要指导者和抽象建造者这两个角色 原型模式适用于创建重复或相似的对象的情况,建造者模式适用于创建复杂对象的情况

    相关文章

      网友评论

          本文标题:建造者模式

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