浅谈设计模式 - 建造者模式(十七)

简介: 浅谈设计模式 - 建造者模式(十七)

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

这个设计模式在lombok其实已经被封装为一个@Builder的注解,所以这个轮子基本不需要自己的造,直接拿来用即可,但是我们还是需要了解这个模式底层是如何实现的,建造者设计模式在个人看来更多是编写出更加“优雅”的代码,特别是参数很多的时候使用建造者模式的链式调用会让代码干净很多。

这里推荐idea的插件GenrateAllGetSet,一件生成一个对象的所有set方法也比较好用,特别是不想编写套版化的建造者对象的时候。

定义

建造者模式将复杂的构建过程和对象的具体展示进行切分,客户端只需要了解建造者所需的参数,不需要了解建造的细节,并且根据构建的操作可以自定义不同的对象。这个模式主要解决的问题是构建过程复杂的问题,并且重点关注对象的构建过程的“配置化”

优缺点

优点:

  1. 构建的产品必须有共同点,不能对于完全不同的产品使用同一个建造器。
  2. 如内部变化复杂,会有很多的建造类,并且很容易出现嵌套。

缺点:

  1. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
  2. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

应用场景

  1. 针对对象的大量set方法,有可能会误传参数的,可以使用建造者的 链式调用构建参数构建对象。
  2. 根据不同的组合参数实现不同的效果套件。
  3. **使用工厂模式+建造者模式不仅可以构建出不同的产品,并且可以定制产品的构建细节。**但是注意不要和工厂混淆了,工厂负责的是解耦生产和使用的过程,而建造者更加关心构建的细节。

结构图:

这个模式比较特殊,个人对于模板代码印象比较深,但是对于这个结构图个人认为没有必要了解,所以这里直接用网络上的截图进行简单介绍:

image.png

Effective java应用第二条

《effective java》这本书中第二条提到了使用构建器来弥补多个构造器参数的缺点,案例如下,通常我们构造器如果参数过多的情况下,如果编写构建器一般会有如下的方法:

public User(String userId, String orgId, String orgName, String merchNo, String userName, String userRealName, String userPwd) {
        this.userId = userId;
        this.orgId = orgId;
        this.orgName = orgName;
        this.merchNo = merchNo;
        this.userName = userName;
        this.userRealName = userRealName;
        this.userPwd = userPwd;
    }

这样的写法既然容易传错参数,并且如果加入boolean的参数判断更是地狱,比如我们构建对象的时候会是这种情况,下面的构造器构建对象的参数顺序基本没有人记得住,基本都是肉眼一一核对才不容易出错:

User user = new User("xx","xx","xx","xx","xx","xx","xx");

正常情况下我们会选择使用set方法替代,比如像下面这样,但是这样的处理方式会直接导致对象的不可变特性被破坏,这里顺带介绍一下前文提到的生成set方法的使用,举例来说,在mac的电脑上,把光标放到对象上面然后按下option+Enter即可(前提保证需要的参数都有set方法,因为插件底层基于对象的方法的反射生成)。

image.png

生成完成之后,就可以发现如下的效果,但是很明显这种方式破坏了对象的封装性

image.png

为了解决上面的问题,我们使用建造者模式的来对于这样的代码进行重构,这也是建造者模式的一个模板代码,这里通过私有构造器,使用建造者对于具体的实例进行一层”保护“,这样直接对于对象的生成过程封闭并且保证对象的线程安全

public class UserBuilder {
    /**
     * 用户id
     */
    private final String userId;
    /**
     * 机构id
     */
    private final String orgId;
    /**
     * 机构名称
     */
    private final String orgName;
    public static class Builder {
        /**
         * 用户id
         */
        private String userId;
        /**
         * 机构id
         */
        private String orgId;
        /**
         * 机构名称
         */
        private String orgName;
        public Builder(String userId, String orgId, String orgName) {
            this.userId = userId;
            this.orgId = orgId;
            this.orgName = orgName;
        }
        public Builder userId(String userId) {
            this.userId = userId;
            return this;
        }
        public Builder orgId(String orgId) {
            this.orgId = orgId;
            return this;
        }
        public Builder orgName(String orgName) {
            this.orgName = orgName;
            return this;
        }
        public UserBuilder build(){
            return new UserBuilder(this);
        }
    }
    private UserBuilder(Builder builder) {
        this.userId = builder.userId;
        this.orgId = builder.orgId;
        this.orgName = builder.orgName;
    }
}

实际案例

这个案例也是对于《effective java》这本书关于构建器的一次简化,这个案例的表现了对于构造器的 进阶用法,对于构造器也可以进行动态扩展,这里直接给出代码了,在书中的案例会更为复杂一点,这里进行了简化。

这个构建器的大致目的是实现动态的性格构建,在顶层的抽象对象和建造者中定义了抽象的方法供子类实现,子类可以实现具体的产品实现的同时可以实现对于建造者的建造器的细节处理,这里同时使用了范型。

public abstract class AbstractUser {
    private final NATURE nature;
    protected enum NATURE {LIVELY, MELANCHOLY, LONELY, NORMAL}
    abstract static class Builder<T extends Builder<T>> {
        private NATURE nature = LIVELY;
        /**
         * 构建方法
         *
         * @return
         */
        protected abstract T build();
        /**
         * 需要由子类实现
         *
         * @return
         */
        protected abstract AbstractUser process();
    }
    public AbstractUser(Builder builder) {
        this.nature = builder.nature;
    }
}

具体的实现产品以及子类化的构建器,可以看到通过这种方式,就可以完全发挥建造者模式的作用了,不仅保证了动态扩展,同时可以保证细节处理和公用的处理进行解耦,但是使用这种设计模式需要一定的编程经验才写得出,同时要对与设计模式 熟练掌握才建议用下面的写法,否则更加建议使用别的设计模式改写,因为这种写法明显对于阅读性有一定的影响。

public class ConcreteUser extends AbstractUser{
    private final NATURE nature;
    private ConcreteUser(ConcreteBuilder concreteBuilder) {
        super(concreteBuilder);
        this.nature = concreteBuilder.nature;
    }
    public static class ConcreteBuilder extends AbstractUser.Builder{
        private final NATURE nature;
        public ConcreteBuilder(NATURE na) {
            this.nature = na;
        }
        @Override
        protected ConcreteUser process() {
            System.err.println("处理逻辑一");
            return new ConcreteUser(this);
        }
        @Override
        public Builder build() {
            return this;
        }
    }
}

总结

总之,建造者模式还是老老实实用注解比较合适,多数情况用注解的方式也能搞定,对于建造者的深层扩展需要深厚的编程经验和技巧支持,这是我们需要进步和学习的点,但是写出这样的代码无疑需要大量的代码编写练习。

写在最后

建造者模式还是十分好理解的一个设计模式,知道模板代码之后就可以快速回忆的一个设计模式。

相关文章
|
7月前
|
设计模式 Java
【设计模式系列笔记】建造者模式
建造者模式是一种创建型设计模式,用于将复杂对象的构建与其表示分离,使构建过程可定制。关键元素包括产品类(定义要构建的对象)、建造者接口(定义构建方法)、具体建造者类(实现构建过程)和指导者类(负责构建过程)。通过建造者模式,客户端可以灵活地创建具有不同表示的复杂对象,提高代码的可读性和可维护性,尤其适用于构建过程复杂且包含多个可选部分的情况。
134 1
|
7月前
|
设计模式 安全 Java
构建未来应用:Java设计模式 - 建造者模式(Builder)在现代编程中的应用
【4月更文挑战第7天】建造者模式是提升代码质量的关键,尤其在复杂环境中。它分步骤构建对象,将构建与表示分离,适用于UI构建、数据模型组装、配置文件解析和网络请求构造等场景。最佳实践包括明确构建步骤、提供默认值、支持链式调用和确保线程安全。然而,过多步骤、不一致状态和性能问题是使用时需注意的问题。掌握建造者模式对于现代编程至关重要。
86 3
|
3月前
|
设计模式 算法
设计模式--建造者模式 builder
这篇文章通过一个电脑购买的例子,详细解释了建造者模式的四个角色(产品类、抽象构建者、实体构建类和指导者类),并提供了相应的代码实现,阐述了建造者模式在设计复杂对象时的应用和优势。
设计模式--建造者模式 builder
|
17天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
3月前
|
设计模式 算法 Java
Java设计模式-建造者模式(6)
Java设计模式-建造者模式(6)
|
4月前
|
设计模式 XML 存储
【四】设计模式~~~创建型模式~~~建造者模式(Java)
文章详细介绍了建造者模式(Builder Pattern),这是一种创建型设计模式,用于将复杂对象的构建与其表示分离,允许分步骤创建一个复杂的对象而无需指定其内部的具体构造细节。通过定义抽象建造者、具体建造者、指挥者和产品角色,建造者模式允许通过相同的构建过程创建不同的产品表示,提高了系统的灵活性和扩展性。
|
6月前
|
设计模式 算法
建造者模式-大话设计模式
建造者模式-大话设计模式
|
7月前
|
设计模式 uml
大话设计模式(3)——造物者一般的建造者模式
大话设计模式(3)——造物者一般的建造者模式
43 1
大话设计模式(3)——造物者一般的建造者模式
|
5月前
|
设计模式 JavaScript
js设计模式【详解】—— 建造者模式
js设计模式【详解】—— 建造者模式
57 0
|
6月前
|
设计模式
设计模式-05建造者模式(Builder Pattern)
设计模式-05建造者模式(Builder Pattern)