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

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

前言


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

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


定义


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


优缺点


优点:

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

缺点:

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


应用场景


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


结构图:



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


网络异常,图片无法展示
|


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方法,因为插件底层基于对象的方法的反射生成)。


网络异常,图片无法展示
|


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


网络异常,图片无法展示
|


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


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
浅谈设计模式 - 建造者模式(十七)
浅谈设计模式 - 建造者模式(十七)
52 1
|
设计模式 Rust Java
二十三种设计模式:工厂模式
在上面的示例中,我们定义了一个抽象的产品类 Product,它包含一个抽象的方法 use(),用于表示产品的使用方法。具体的产品类 ProductA 和 ProductB 继承自 Product,并实现了 use() 方法。
82 0
|
设计模式 Java
设计模式笔记 -- 建造者模式
建造者模式又叫生成器模式,是一种对象构建模式。可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同属性的对象
|
设计模式 Java
设计模式笔记 -- 工厂模式
现在有一个发快递的流程要实现,如果用传统的方式实现,代码是下面这样
|
设计模式 Go
二十三天搞懂设计模式之抽象工厂模式
二十三天搞懂设计模式之抽象工厂模式
二十三天搞懂设计模式之抽象工厂模式
|
设计模式 Java
设计模式轻松学【十七】原型模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效。就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
147 0
设计模式轻松学【十七】原型模式
|
设计模式
设计模式轻松学【十六】建造者模式
在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。
141 0
设计模式轻松学【十六】建造者模式
|
设计模式 Java uml
设计模式轻松学【十一】装饰模式
如果要扩展一些功能,我们可以采用装饰模式来实现。装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰者模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。
139 0
设计模式轻松学【十一】装饰模式
|
设计模式
二十三种设计模式 之 初识设计模式
二十三种设计模式 之 初识设计模式
146 0