设计模式 - 构建者模式 Builder Pattern

文章介绍了在Java和Kotlin中如何使用构造者模式来处理构造函数参数过多和可选参数的问题。通过对比构造函数重载、JavaBean方式,强调了构建者模式在创建对象时的清晰性和灵活性。在Kotlin中,由于默认参数和命名参数,通常情况下不需要构建者模式,但在特定场景下,如对象需要多步构建且保持不可变性时,依然适用。

一、概念

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。假设要创建一个 Computer 对象,cpu、ram是必选,display、keyboard是可选。

优点建造者独立,易扩展。
便于控制细节风险。
缺点产品需有共同点,适用范围有限。
若内部变化复杂,会产生大量建造类。

1.1 与工厂模式区别

工厂主要变更的地方是产品的变更,而Builder模式主要变更的地方是director(组装逻辑)。工厂更倾向基本组件的生产,而Builder是这些基本组件的组装。

1.2 Android 使用场景

 Dialog创建如自定义对话框,通过Builder类分步设置标题、内容、按钮、自定义视图等,避免多参数构造函数的混乱。  
Notification创建系统NotificationCompat.Builder就是典型应用,通过setContentTitle()、setSmallIcon()等方法配置通知,最终build()生成实例。
网络请求参数组装Retrofit或OkHttp中,通过建造者模式设置url、headers、params、method等,构建完整的请求对象。

二、Java写法

2.1 构造函数重载(不推荐)

阅读起来不方便,参数太多容易混淆。

class Computer {
    private String cpu;     //必选
    private String ram;     //必选
    private String display;     //可选
    private String keyboard;    //可选
    
    public Computer(String cpu, String ram) {
        this(cpu, ram, "LG显示器");
    }
    public Computer(String cpu, String ram, String display) {
        this(cpu, ram, display, "罗技鼠标");
    }
    public Computer(String cpu, String ram, String display, String keyboard) {
        this.cpu = cpu;
        this.ram = ram;
        this.display = display;
        this.keyboard = keyboard;
    }
}

2.2  JavaBean

构建过程中对象状态容易发生变化,分步设置属性容易出错。

class Computer {
    private String cpu;     //必选
    private String ram;     //必选
    private String display = "LG";     //可选
    private String keyboard = "罗技";    //可选

    public Computer(String cpu, String ram) {
        this.cpu = cpu;
        this.ram = ram;
    }
    //setter和getter
    public String getCpu() {return cpu;}
    public void setCpu(String cpu) {this.cpu = cpu;}
    public String getRam() {return ram;}
    public void setRam(String ram) {this.ram = ram;}
    public String getDisplay() {return display;}
    public void setDisplay(String display) {this.display = display;}
    public String getKeyboard() {return keyboard;}
    public void setKeyboard(String keyboard) {this.keyboard = keyboard;}
}

2.3 构建者模式

class Computer {
    private final String cpu;     //必选
    private final String ram;     //必选
    private final String display;     //可选
    private final String keyboard;    //可选

    //目标类中创建private的构造函数,形参类型为Builder。
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.display = builder.display;
        this.keyboard = builder.keyboard;
    }

    //目标类中创建静态内部类Builder,将参数都复制到Builder中。
    public static class Builder {
        private String cpu;
        private String ram;
        private String display;
        private String keyboard;

        //Builder中创建public的构造函数,参数为目标类中必填的参数(此处为cpu、ram)。
        public Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        //Builder中创建设置函数,对可选参数进行赋值(此处为display、keyboard),返回Builder实例。
        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }
        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }
        public String getDisplay() { return display; }
        public String getKeyboard() { return keyboard; }

        //Builder中创建build()方法,返回目标类的实例。
        public Computer build() {
            return new Computer(this);
        }
    }
}
//使用
Computer computer = new Computer.Builder("因特尔", "三星")
        .setDisplay("LG")
        .setKeyboard("罗技")
        .build();

三、Kotlin写法

由于 Kotlin 支持默认参数(形参有默认值)、命名参数(乱序指定形参名赋值)、作用域函数(apply),大部分情况下并不需要构建者模式,除了当我们希望一个对象经过N步才能完成构建且对象不可变时。假设 Computer 需要配置 cpu、ram 才是一个完整的对象,但它俩不能同时到位,如果先创建对象再后期 setter 进去,Computer就是可变对象了。

//目标类添加private主构造,属性只读供外部使用。
class Computer private constructor(
    val cpu: String,
    val ram: String,
    val display: String,
    val keyboard: String,
) {
    //目标类中创建内部类Builder(Kotlin的内部类是static的)。
    //必传参数放在主构造,可选参数放在属性
    class Builder(val cpu: String, val ram: String) {
        //私有化属性并提供自定义的方法对属性赋值(为了可以链式调用)。
        private var display: String = "LG"
        private var keyboard: String = "罗技"
        fun setDisplay(str: String) = apply {display = str}
        fun setKeyboard(str: String) = apply {keyboard = str}
        //创建build()方法,创建目标类实例并返回
        fun build() = Computer(
            cpu = cpu,
            ram = ram,
            display = display,
            keyboard = keyboard
        )
    }
}
//使用
val computer = Computer.Builder("因特尔","三星")
    .setDisplay("LG")
    .setKeyboard("罗技")
    .build()
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值