# 建造者模式(Builder Pattern)

目录: https://note.dolyw.com/design/ (opens new window)

代码地址

# 1. 介绍

建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的

# 1.1. 特点

待补充

# 1.2. 结构

待补充

# 1.3. 优缺点

  • 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示
  • 主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定
  • 何时使用:一些基本部件不会变,而其组合经常变化的时候 如何解决:将变与不变分离开
  • 关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系
  • 使用场景:需要生成的对象具有复杂的内部结构。需要生成的对象内部属性本身相互依赖
  • 优点:
    • 建造者独立,易扩展
    • 便于控制细节风险
  • 缺点:
    • 产品必须有共同点,范围有限制
    • 如内部变化复杂,会有很多的建造类
  • 应用实例:JAVA 中的 StringBuilder
  • 注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序

# 2. 代码

常用模式,直接看使用

# 3. 使用

# 3.1. IDEA实现

使用 IDEA 插件 Builder-Generator

/**
 * 应用
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/2/11 14:13
 */
public class App {

    /** 主键ID */
    private Long appId;

    /** 应用代码 */
    private String appCode;

    /** 应用名称 */
    private String appName;

    /** 应用私钥 */
    private String appSecret;

    /** 应用类型 */
    private String appType;

    /** 创建人 */
    private String createdBy;

    /** 创建时间 */
    private Date createdTime;

    public Long getAppId() {
        return appId;
    }

    public void setAppId(Long appId) {
        this.appId = appId;
    }

    public String getAppCode() {
        return appCode;
    }

    public void setAppCode(String appCode) {
        this.appCode = appCode;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }

    public String getAppType() {
        return appType;
    }

    public void setAppType(String appType) {
        this.appType = appType;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public Date getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }

    /**
     * 建造者模式
     *
     * @return
     */
    public static Builder Builder() {
        return new Builder();
    }

    /**
     * 建造者模式 - Builder内部类
     */
    public static final class Builder {
        private Long appId;
        private String appCode;
        private String appName;
        private String appSecret;
        private String appType;
        private String createdBy;
        private Date createdTime;

        private Builder() {
        }

        public Builder withAppId(Long appId) {
            this.appId = appId;
            return this;
        }

        public Builder withAppCode(String appCode) {
            this.appCode = appCode;
            return this;
        }

        public Builder withAppName(String appName) {
            this.appName = appName;
            return this;
        }

        public Builder withAppSecret(String appSecret) {
            this.appSecret = appSecret;
            return this;
        }

        public Builder withAppType(String appType) {
            this.appType = appType;
            return this;
        }

        public Builder withCreatedBy(String createdBy) {
            this.createdBy = createdBy;
            return this;
        }

        public Builder withCreatedTime(Date createdTime) {
            this.createdTime = createdTime;
            return this;
        }

        public App build() {
            App app = new App();
            app.setAppId(appId);
            app.setAppCode(appCode);
            app.setAppName(appName);
            app.setAppSecret(appSecret);
            app.setAppType(appType);
            app.setCreatedBy(createdBy);
            app.setCreatedTime(createdTime);
            return app;
        }
    }
}
/**
 * 建造者模式
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/2/11 14:27
 */
public class Main {

    public static void main(String[] args) {
        App app = App.Builder()
                .withAppId(1L)
                .withAppCode("App")
                .withAppName("XX系统")
                .withCreatedBy("XXX")
                .build();
        Channel channel = Channel.builder()
                .channelId(1L)
                .channelName("XXX")
                .channelType(1)
                .createdTime(new Date())
                .build();
    }
}

# 3.2. Lombok

实体添加 @Builder 注解即可

/**
 * 渠道
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/2/11 14:13
 */
@Data
@Builder
public class Channel {

    /** 主键ID */
    private Long channelId;

    /** 渠道代码 */
    private String channelCode;

    /** 渠道名称 */
    private String channelName;

    /** 渠道类型 */
    private Integer channelType;

    /** 渠道状态:1-待审核 2-审核通过 3-合作中 4-暂停合作 5-废弃 */
    private Integer status;

    /** 渠道生效日期 */
    private Date effectiveDate;

    /** 渠道失效日期 */
    private Date invalidDate;

    /** 渠道联系人 */
    private String channelContactName;

    /** 渠道联系电话 */
    private String channelContactPhone;

    /** 渠道联系邮箱 */
    private String channelContactEmail;

    /** 创建人 */
    private String createdBy;

    /** 创建时间 */
    private Date createdTime;

}
/**
 * 建造者模式
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/2/11 14:27
 */
public class Main {

    public static void main(String[] args) {
        App app = App.Builder()
                .withAppId(1L)
                .withAppCode("App")
                .withAppName("XX系统")
                .withCreatedBy("XXX")
                .build();
        Channel channel = Channel.builder()
                .channelId(1L)
                .channelName("XXX")
                .channelType(1)
                .createdTime(new Date())
                .build();
    }
}

# 3.3. AlertDialog

通常是一个对象,具有多个成员变量可能需要初始化,常规方法,需要提供大量构造函数。例如:

/**
 * 非 Android 中的 AlertDialog,便于说明问题,举个例子
 */
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;

    private AlertDialog() {
    }

    /**
     * 空白的警告框
     *
     * @param width
     * @param height
     */
    public AlertDialog(int width, int height) {
        AlertDialog(width, height, null);
    }

    /**
     * 带标题的警告框
     *
     * @param width
     * @param height
     * @param title
     */
    public AlertDialog(int width, int height, String title) {
        AlertDialog(width, height, title, "确定");
    }

    /**
     * 带标题的警告框,有确定按钮
     *
     * @param width
     * @param height
     * @param title
     * @param confirm
     */
    public AlertDialog(int width, int height, String title, String confirm) {
        AlertDialog(width, height, title, confirm, null);
    }

    /**
     * 带标题的警告框,有确定按钮,取消按钮
     *
     * @param width
     * @param height
     * @param title
     * @param confirm
     * @param denyText
     */
    public AlertDialog(int width, int height, String title, String confirm, String denyText) {
        // set every thing.
    }
}

有多种样式的警告框,为了调用方便,必须提供多个构造函数。否则用户在调用时,只能使用完整构造函数,容易犯错且无法进行阅读。极不灵活。如果采用另外一种方式,则可以解决,但会花费很多经历处理并发的情况:

/**
 * 非 Android 中的 AlertDialog,便于说明问题,举个例子
 */
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;

    /**
     * 空白的构造函数
     */
    public AlertDialog() {
    }

    public void setWidth(int width) {
        this.width = width;
    }
    // 其他set方法
}

调用时,通过调用各个参数的set方法进行设置。问题来了:

并发 无法进行参数校验。 例如,只创建了对象,设置了标题,却没有尺寸,相当于创建了一个没有尺寸的警告框

在 Android 中,大量的控件都使用了构造器 Builder

/**
 * 非 Android 中的 AlertDialog,便于说明问题,举个例子
 */
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;

    /**
     * private
     */
    private AlertDialog() {
    }

    /**
     * Builder中使用
     *
     * @param b
     */
    protected AlertDialog(Builder b) {
        width = b.width;
        height = b.height;
        // .....
        if (width == 0 || height == 0) {
            throws new Exception("size must be set");
        }
    }

    /**
     * 构造器
     */
    public static class Builder {
        private int width;
        private int height;
        private String title;
        private String confirmText;
        private String denyText;

        /**
         * 注意:返回的Builder
         *
         * @param title
         * @return
         */
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        // 其他set...

        public AlertDialog build() {
            return AlertDialog(this);
        }
    }
}

于是,可以根据相应需求,进行相应设置,并在AlertDialog真正构造时,进行参数校验。就像这样:

new AlertDialog.Builder().setTitle("提示").build();

上述例子,会成功抛出异常

上次更新时间: 2023-12-15 03:14:55