【Java设计模式 设计模式与范式】创建型模式 五:建造者模式(上)

简介: 【Java设计模式 设计模式与范式】创建型模式 五:建造者模式(上)

本篇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方法...
}


相关文章
|
6天前
|
设计模式 测试技术 Python
《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
【7月更文挑战第10天】Page Object Model (POM)是Selenium自动化测试中的设计模式,用于提高代码的可读性和维护性。POM将每个页面表示为一个类,封装元素定位和交互操作,使得测试脚本与页面元素分离。当页面元素改变时,只需更新对应页面类,减少了脚本的重复工作和维护复杂度,有利于团队协作。POM通过创建页面对象,管理页面元素集合,将业务逻辑与元素定位解耦合,增强了代码的复用性。示例展示了不使用POM时,脚本直接混杂了元素定位和业务逻辑,而POM则能解决这一问题。
24 6
|
4天前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
12 2
|
5天前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
【7月更文挑战第11天】页面对象模型(POM)通过Page Factory在Java Selenium测试中被应用,简化了代码维护。在POM中,每个网页对应一个Page Class,其中包含页面元素和相关操作。对比之下,非POM实现直接在测试脚本中处理元素定位和交互,代码可读性和可维护性较低。
|
7天前
|
设计模式 存储 缓存
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
10 0
|
7天前
|
设计模式 缓存 安全
Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
7 0
|
8天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
41 8
|
16天前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 构造函数模式
js设计模式【详解】—— 构造函数模式
18 6
|
21天前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
17 2
|
22天前
|
设计模式 搜索推荐
工厂方法模式-大话设计模式
工厂方法模式-大话设计模式
11 1
|
27天前
|
设计模式 算法
行为型设计模式之模板模式
行为型设计模式之模板模式