前言
这个设计模式在lombok
其实已经被封装为一个@Builder
的注解,所以这个轮子基本不需要自己的造,直接拿来用即可,但是我们还是需要了解这个模式底层是如何实现的,建造者设计模式在个人看来更多是编写出更加“优雅”的代码,特别是参数很多的时候使用建造者模式的链式调用会让代码干净很多。
这里推荐idea的插件GenrateAllGetSet,一件生成一个对象的所有set方法也比较好用,特别是不想编写套版化的建造者对象的时候。
定义
建造者模式将复杂的构建过程和对象的具体展示进行切分,客户端只需要了解建造者所需的参数,不需要了解建造的细节,并且根据构建的操作可以自定义不同的对象。这个模式主要解决的问题是构建过程复杂的问题,并且重点关注对象的构建过程的“配置化”。
优缺点
优点:
- 构建的产品必须有共同点,不能对于完全不同的产品使用同一个建造器。
- 如内部变化复杂,会有很多的建造类,并且很容易出现嵌套。
缺点:
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
应用场景
- 针对对象的大量
set
方法,有可能会误传参数的,可以使用建造者的 链式调用构建参数构建对象。 - 根据不同的组合参数实现不同的效果套件。
- **使用工厂模式+建造者模式不仅可以构建出不同的产品,并且可以定制产品的构建细节。**但是注意不要和工厂混淆了,工厂负责的是解耦生产和使用的过程,而建造者更加关心构建的细节。
结构图:
这个模式比较特殊,个人对于模板代码印象比较深,但是对于这个结构图个人认为没有必要了解,所以这里直接用网络上的截图进行简单介绍:
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; } } }
总结
总之,建造者模式还是老老实实用注解比较合适,多数情况用注解的方式也能搞定,对于建造者的深层扩展需要深厚的编程经验和技巧支持,这是我们需要进步和学习的点,但是写出这样的代码无疑需要大量的代码编写练习。
写在最后
建造者模式还是十分好理解的一个设计模式,知道模板代码之后就可以快速回忆的一个设计模式。