本篇Blog继续学习创建型模式,创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离,这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。本篇学习的是建造者模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:
- 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景等
- 模式结构:包含模式的结构,包含的角色定义及调用关系
- 模式实现:包含模式的实现方式代码举例或者生活中简单问题映射代码举例
- 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
- 模式对比:如果模式相似或模式有额外的替换方法,有必要体现其相似点及不同点,区分使用,说明哪些场景下使用哪种模式比较好
- 模式扩展:如果模式有与标准结构定义不同的变体形式,一并体现出其变体结构;对模式的思考需要进行发散等。
接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,如果某一条目没有则无需体现,但条目顺序遵循此结构,本文的模式实践案例大多来自极客时间。
模式档案
模式定义:建造者模式(Builder Pattern)指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的
模式特点:主要特点是将变与不变分离开,建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
解决什么问题:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定
优点:封装性好,构建和表示分离;扩展性好,各个具体的建造者相互独立,有利于系统的解耦;客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
缺点:产品的组成部分必须相同,这限制了其使用范围;如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
使用场景: 当需要生成的对象具有复杂的内部结构且需要生成的对象内部属性本身相互依赖。
- 相同的方法,不同的执行顺序,产生不同的结果。
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值
符合以上条件可以考虑建造者模式
模式结构
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者 4 个角色构成
- 产品(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
角色的相互调用关系如下图所示,需要注意的是:建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色
模式实现
依据模式结构中的角色进行代码实现,如下所示:
产品
产品:包含多个组成部件的复杂对象
package com.example.designpattern.builder; import lombok.Setter; @Setter public class Product { private String partA; private String partB; private String partC; public void show(){ System.out.println(partA+" ; "+partB+" ; "+partC); } }
抽象建造者
抽象建造者:包含创建产品各个子部件的抽象方法
package com.example.designpattern.builder; public abstract class Builder { //创建产品对象 protected Product product = new Product(); public abstract void buildPartA(); public abstract void buildPartB(); public abstract void buildPartC(); //返回产品对象 public Product getResult() { return product; } }
具体建造者
具体建造者:实现了抽象建造者接口
package com.example.designpattern.builder; public class ConcreteBuilder extends Builder { public void buildPartA() { product.setPartA("建造 PartA"); } public void buildPartB() { product.setPartB("建造 PartB"); } public void buildPartC() { product.setPartC("建造 PartC"); } }
指挥者
指挥者:调用建造者中的方法完成复杂对象的创建
package com.example.designpattern.builder; import lombok.AllArgsConstructor; // 将一个复杂的构建过程与其表示相分离 @AllArgsConstructor public class Director { // 针对接口编程,而不是针对实现编程 private Builder builder; //产品构建与组装方法 public Product construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.getResult(); } }
客户端调用代码如下
package com.example.designpattern.builder; public class Client { public static void main(String[] args) { Builder builder = new ConcreteBuilder(); Director director = new Director(builder); Product product = director.construct(); product.show(); } }
调用结果如下:
建造 PartA ; 建造 PartB ; 建造 PartC Process finished with exit code 0
模式实践
我们来看看如下两种模式实践:客厅装修建造和资源池配置建造
设计一个客厅装修建造方案
用建造者(Builder)模式描述客厅装修。客厅装修是一个复杂的过程,它包含墙体的装修、电视机的选择、沙发的购买与布局等。客户把装修要求告诉项目经理,项目经理指挥装修工人一步步装修,最后完成整个客厅的装修与布局。
- 客厅是产品,包括墙、电视和沙发等组成部分。
- 抽象装修工人是抽象建造者,这里并没有实例对应,可以理解为装修工人们的培训师傅吧。
- 具体装修工人是具体建造者,他们负责装修与墙、电视和沙发的布局。
- 项目经理是指挥者,他负责指挥装修工人进行装修。
另外,客厅类中提供了 show() 方法,可以将装修效果图显示出来。整体代码结构如下:
//产品:客厅 @Setter class Parlour { private String wall; //墙 private String TV; //电视 private String sofa; //沙发 public void show() { System.out.println(wall+" ; "+TV+" ; "+sofa); } } //抽象建造者:装修工人 abstract class Decorator { //创建产品对象 protected Parlour product = new Parlour(); public abstract void buildWall(); public abstract void buildTV(); public abstract void buildSofa(); //返回产品对象 public Parlour getResult() { return product; } } //具体建造者:具体装修工人1 class ConcreteDecorator1 extends Decorator { public void buildWall() { product.setWall("wall1"); } public void buildTV() { product.setTV("TV1"); } public void buildSofa() { product.setSofa("sofa1"); } } //具体建造者:具体装修工人2 class ConcreteDecorator2 extends Decorator { public void buildWall() { product.setWall("wall2"); } public void buildTV() { product.setTV("TV2"); } public void buildSofa() { product.setSofa("sofa2"); } } //指挥者:项目经理 class ProjectManager { private Decorator builder; public ProjectManager(Decorator builder) { this.builder = builder; } //产品构建与组装方法 public Parlour decorate() { builder.buildWall(); builder.buildTV(); builder.buildSofa(); return builder.getResult(); } } //客户端 package com.example.designpattern.builder; public class Client { public static void main(String[] args) { Decorator builder = new Decorator(); ProjectManager director = new ProjectManager(builder); Parlour product = director.construct(); product.show(); } }
设计一个资源池配置建造最优方案
在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。什么情况下这种方式就不适用了,需要采用建造者模式来创建对象呢,举个资源池配置建造的例子:我们需要定义一个资源池配置类 ResourcePoolConfig
。这里的资源池,可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项,我们自然而然的想到用构造函数直接创建:
public class ResourcePoolConfig { private static final int DEFAULT_MAX_TOTAL = 8; private static final int DEFAULT_MAX_IDLE = 8; private static final int DEFAULT_MIN_IDLE = 0; private String name; private int maxTotal = DEFAULT_MAX_TOTAL; private int maxIdle = DEFAULT_MAX_IDLE; private int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig(String name, Integer maxTotal, Integer maxIdle, Integer minIdle) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("name should not be empty."); } this.name = name; if (maxTotal != null) { if (maxTotal <= 0) { throw new IllegalArgumentException("maxTotal should be positive."); } this.maxTotal = maxTotal; } if (maxIdle != null) { if (maxIdle < 0) { throw new IllegalArgumentException("maxIdle should not be negative."); } this.maxIdle = maxIdle; } if (minIdle != null) { if (minIdle < 0) { throw new IllegalArgumentException("minIdle should not be negative."); } this.minIdle = minIdle; } } //...省略getter方法... }