Java设计模式14:建造者模式

什么是建造者模式

发现很多框架的源码使用了建造者模式,看了一下觉得挺实用的,就写篇文章学习一下,顺便分享给大家。

建造者模式是什么呢?用一句话概括就是建造者模式的目的是为了分离对象的属性与创建过程,是的,只要记住并理解红字的几个部分,建造者模式你就懂了。

为什么需要建造者模式

建造者模式是构造方法的一种替代方案,为什么需要建造者模式,我们可以想,假设有一个对象里面有20个属性:

  • 属性1
  • 属性2
  • ...
  • 属性20

对开发者来说这不是疯了,也就是说我要去使用这个对象,我得去了解每个属性的含义,然后在构造函数或者Setter中一个一个去指定。更加复杂的场景是,这些属性之间是有关联的,比如属性1=A,那么属性2只能等于B/C/D,这样对于开发者来说更是增加了学习成本,开源产品这样的一个对象相信不会有太多开发者去使用。

为了解决以上的痛点,建造者模式应运而生,对象中属性多,但是通常重要的只有几个,因此建造者模式会让开发者指定一些比较重要的属性或者让开发者指定某几个对象类型,然后让建造者去实现复杂的构建对象的过程,这就是对象的属性与创建分离。这样对于开发者而言隐藏了复杂的对象构建细节,降低了学习成本,同时提升了代码的可复用性。

虽然感觉基本说清楚了,但还是有点理论,具体往下看一下例子。

建造者模式代码示例

举一个实际场景的例子:

大家知道一辆车是很复杂的,有发动机、变速器、轮胎、挡风玻璃、雨刮器、气缸、方向盘等等无数的部件。

用户买车的时候不可能一个一个去指定我要那种类型的变速器、我要一个多大的轮胎、我需要长宽高多少的车,这是不现实的

通常用户只会和销售谈我需要什么什么样的类型的车,马力要不要强劲、空间是否宽敞,这样销售就会根据用户的需要去推荐一款具体的车

这就是一个典型建造者的场景:车是复杂对象,销售是建造者。我告诉建造者我需要什么,建造者根据我的需求给我一个具体的对象

根据这个例子,我们定义一个简单的汽车对象:

 1 public class Car {
 2
 3     // 尺寸
 4     private String size;
 5
 6     // 方向盘
 7     private String steeringWheel;
 8
 9     // 底座
10     private String pedestal;
11
12     // 轮胎
13     private String wheel;
14
15     // 排量
16     private String displacement;
17
18     // 最大速度
19     private String maxSpeed;
20
21     public String getSize() {
22         return size;
23     }
24
25     public void setSize(String size) {
26         this.size = size;
27     }
28
29     public String getSteeringWheel() {
30         return steeringWheel;
31     }
32
33     public void setSteeringWheel(String steeringWheel) {
34         this.steeringWheel = steeringWheel;
35     }
36
37     public String getPedestal() {
38         return pedestal;
39     }
40
41     public void setPedestal(String pedestal) {
42         this.pedestal = pedestal;
43     }
44
45     public String getWheel() {
46         return wheel;
47     }
48
49     public void setWheel(String wheel) {
50         this.wheel = wheel;
51     }
52
53     public String getDisplacement() {
54         return displacement;
55     }
56
57     public void setDisplacement(String displacement) {
58         this.displacement = displacement;
59     }
60
61     public String getMaxSpeed() {
62         return maxSpeed;
63     }
64
65     public void setMaxSpeed(String maxSpeed) {
66         this.maxSpeed = maxSpeed;
67     }
68
69     @Override
70     public String toString() {
71         return "Car [size=" + size + ", steeringWheel=" + steeringWheel + ", pedestal=" + pedestal + ", wheel=" + wheel
72             + ", displacement=" + displacement + ", maxSpeed=" + maxSpeed + "]";
73     }
74
75 }

这里简单定义几个参数,然后建造者对象应运而生:

public class CarBuilder {

    // 车型
    private String type;

    // 动力
    private String power;

    // 舒适性
    private String comfort;

    public Car build() {
        Assert.assertNotNull(type);
        Assert.assertNotNull(power);
        Assert.assertNotNull(comfort);

        return new Car(this);
    }

    public String getType() {
        return type;
    }

    public CarBuilder type(String type) {
        this.type = type;
        return this;
    }

    public String getPower() {
        return power;
    }

    public CarBuilder power(String power) {
        this.power = power;
        return this;
    }

    public String getComfort() {
        return comfort;
    }

    public CarBuilder comfort(String comfort) {
        this.comfort = comfort;
        return this;
    }

    @Override
    public String toString() {
        return "CarBuilder [type=" + type + ", power=" + power + ", comfort=" + comfort + "]";
    }

}

说是建造者,其实也不合适,它只是一个中间对象,用于接收来自外部的信息,比如需要什么样的车型,需要什么样的动力啊这些。

然后大家一定注意到了build方法,这个是建造者模式好像约定俗成的方法名,代表建造,里面把自身对象传给Car,这个构造方法的实现我在第一段代码里面是没有贴的,这段代码的实现为:

public Car(CarBuilder builder) {
    if ("紧凑型车".equals(builder.getType())) {
        this.size = "大小--紧凑型车";
    } else if ("中型车".equals(builder.getType())) {
        this.size = "大小--中型车";
    } else {
        this.size = "大小--其他";
    }

    if ("很舒适".equals(builder.getComfort())) {
        this.steeringWheel = "方向盘--很舒适";
        this.pedestal = "底座--很舒适";
    } else if ("一般舒适".equals(builder.getComfort())) {
        this.steeringWheel = "方向盘--一般舒适";
        this.pedestal = "底座--一般舒适";
    } else {
        this.steeringWheel = "方向盘--其他";
        this.pedestal = "底座--其他";
    }

    if ("动力强劲".equals(builder.getPower())) {
        this.displacement = "排量--动力强劲";
        this.maxSpeed = "最大速度--动力强劲";
        this.steeringWheel = "轮胎--动力强劲";
    } else if ("动力一般".equals(builder.getPower())) {
        this.displacement = "排量--动力一般";
        this.maxSpeed = "最大速度--动力一般";
        this.steeringWheel = "轮胎--动力一般";
    } else {
        this.displacement = "排量--其他";
        this.maxSpeed = "最大速度--其他";
        this.steeringWheel = "轮胎--其他";
    }
}

这是真实构建对象的地方,无论多复杂的逻辑都在这里实现而不需要暴露给开发者,还是那句核心的话:实现了对象的属性与构建的分离

这样用起来就很简单了:

@Test
public void test() {
    Car car = new CarBuilder().comfort("很舒适").power("动力一般").type("紧凑型车").build();

    System.out.println(JSON.toJSONString(car));
}

只需要指定我需要什么什么类型的车,然后具体的每个参数自然根据我的需求列出来了,不需要知道每个细节,我也能得到我需要的东西。

建造者模式在开源框架中的应用

文章的开头有说很多开源框架使用了建造者模式,典型的有Guava的Cache、ImmutableMap,不过感觉MyBatis更为大家熟知,且MyBatis内部大量使用了建造者模式,我们可以一起来看一下。

以原生的MyBatis(即不使用Spring框架进行整合)为例,通常使用MyBatis我们会用以下几句代码:

// MyBatis配置文件路径
String resources = "mybatis_config.xml";
// 获取一个输入流
Reader reader = Resources.getResourceAsReader(resources);
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 打开一个会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 具体操作
...

关键我们看就是这个SqlSessionFactoryBuilder,它的源码核心方法实现为:

public class SqlSessionFactoryBuilder {

  ...

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  ...

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  ...

}

因为MyBatis内部是很复杂的,核心类Configuration属性多到爆炸,比如拿数据库连接池来说好了,有POOLED、UNPOOLED、JNDI三种,然后POOLED里面呢又有各种超时时间、连接池数量的设置,这一个一个都要让开发者去设置那简直要命了。因此MyBatis在SqlSessionFactory这一层使用了Builder模式,对开发者隐藏了XML文件解析细节,Configuration内部每个属性赋值细节,开发者只需要指定一些必要的参数(比如数据库地址、用户名密码之类的),就可以直接使用MyBatis了,至于可选参数,配置了就拿开发者配置的,没有配置就默认来一套。

通过这样一种方式,开发者接入MyBatis的成本被降到了最低,这么一种编程方式非常值得大家学习,尤其是自己需要写一些框架的时候。

同样的大家可以看一下Environment,Environment也使用了建造者模式,但是Environment使用建造者模式最大的作用是让用户无法在运行时修改任何环境属性保证了安全与稳定性,同样这也是建造者模式的一种经典实现。

建造者模式的类关系图

其实,建造者模式不像一些设计模式有比较固定或者比较类似的实现方式,它的核心只是分离对象属性与创建,整个实现比较自由,我们可以看到我自己写的造车的例子和SqlSessionFactoryBuilder就明显不是一种实现方式。

看了一些框架源码总结起来,建造者模式的实现大致有两种写法:

这是一种在Builder里面直接new对象的方式,MyBatis的SqlSessionFactoryBuilder就是这种写法,适用于属性之间关联不多且大量属性都有默认值的场景

另外一种就是间接new的方式了:

我的代码示例,还有例如Guava的Cache都是这种写法,适用于属性之间有一定关联性的场景,例如车的长宽高与轴距都属于车型一类、排量与马力都与性能相关,可以把某几个属性归类,然后让开发者指定大类即可。

总体而言,两种没有太大的优劣之分,在合适的场景下选择合适的写法就好了。

建造者模式的优点及适用场景

建造者模式这种设计模式,优缺点比较明显。从优点来说:

  • 客户端不比知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 可以更加精细地控制产品的创建过程,将复杂对象分门别类抽出不同的类别来,使得开发者可以更加方便地得到想要的产品

想了想,说缺点,建造者模式说不上缺点,只能说这种设计模式的使用比较受限:

  • 产品属性之间差异很大且属性没有默认值可以指定,这种情况是没法使用建造者模式的,我们可以试想,一个对象20个属性,彼此之间毫无关联且每个都需要手动指定,那么很显然,即使使用了建造者模式也是毫无作用

总的来说,在IT这个行业,复杂的需求、复杂的业务逻辑层出不穷,这必然导致复杂的业务对象的增加,建造者模式是非常有用武之地的。合理分析场景,在合适的场景下使用建造者模式,一定会使得你的代码漂亮得多。

原文地址:https://www.cnblogs.com/xrq730/p/11344541.html

时间: 2024-07-29 10:34:16

Java设计模式14:建造者模式的相关文章

java设计模式之建造者模式

本文继续介绍23种设计模式系列之建造者模式. 定义: 建造者模式:将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 实用范围 1.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时. 2.当构造过程必须允许被构造的对象有不同表示时. 角色 在这样的设计模式中,有以下几个角色: 1.Builder:为创建一个产品对象的各个部件指定抽象接口. 2.ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示

java设计模式_建造者模式

/**  *   */ package com.wangbiao.design.builder; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /**  * @Title: Product.java  * @Package com.wangbiao.design.builder  * @Description: TODO   * @author wangbiao     * @date 

java设计模式之四建造者模式(Builder)

工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的.我们看一下代码: 还和前面一样,一个Sender接口,两个实现类MailSender和SmsSender.最后,建造者类如下: [java] view plaincopy public class Builder { private List<Sender> list = new ArrayL

java设计模式之-建造者模式

建造者模式能够将复杂的构建与其表示相分离,是的同样的构建过程可以创建出不同的表示. 建造者模式与抽象工厂的区别是:在建造者模式里,有个指导者,这个指导者来管理建造者,用户与指导者相互联系,指导者联系建造者,最终得到我们需要的产品.即建造者模式可以强制实行一种分步骤进行的建造过程. 建造者模式是将复杂的的内部创建封装在内部,对于外部调用的人员来说,只要传入指导者和建造者就可以了,具体内部怎样建造成为产品,用户不用知道,不同的产品需要定义不同的建造者. 概念: 1.抽象建造者(builder):此类

【java】java 设计模式(4):建造者模式(Builder)

工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的.我们看一下代码: 还和前面一样,一个Sender接口,两个实现类MailSender和SmsSender.最后,建造者类如下: public class Builder { private List<Sender> list = new ArrayList<Sender>(); p

大话设计模式_建造者模式(Java代码)

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 简单描述:1个产品类(可有可无,关键是建造流程),1个抽象建造步骤类,多个具体建造子类(不同的类建造不同的产品),1个指挥者(用于规定建造流程),客户端指定需要建造的具体类型,由指挥者建造好之后,建造者子类返回对应产品给客户 大话设计模式中的截图: 例子代码: Product类: 1 package com.longsheng.builder; 2 3 public class Product { 4 5 pr

java设计模式--行为型模式--迭代模式

1 迭代器模式 2 概述 3 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. 4 5 6 适用性 7 1.访问一个聚合对象的内容而无需暴露它的内部表示. 8 9 2.支持对聚合对象的多种遍历. 10 11 3.为遍历不同的聚合结构提供一个统一的接口(即,支持多态迭代). 12 13 14 参与者 15 1.Iterator 16 迭代器定义访问和遍历元素的接口. 17 18 2.ConcreteIterator 19 具体迭代器实现迭代器接口.

java设计模式--行为型模式--命令模式

1 命令模式 2 概述 3 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤消的操作. 4 5 6 适用性 7 1.抽象出待执行的动作以参数化某对象. 8 9 2.在不同的时刻指定.排列和执行请求. 10 11 3.支持取消操作. 12 13 4.支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍. 14 15 5.用构建在原语操作上的高层操作构造一个系统. 16 17 18 参与者 19 1.Command 20 声明执行操作的接口.

C#设计模式之四建造者模式(Builder Pattern)【创建型】

原文:C#设计模式之四建造者模式(Builder Pattern)[创建型] 一.引言 今天我们要讲讲Builder模式,也就是建造者模式,当然也有叫生成器模式的,英文名称是Builder Pattern.在现实生活中,我们经常会遇到一些构成比较复杂的物品,比如:电脑,它就是一个复杂的物品,它主要是由CPU.主板.硬盘.显卡.机箱等组装而成的.手机当然也是复杂物品,由主板,各种芯片,RAM 和ROM  摄像头之类的东西组成.但是无论是电脑还是手机,他们的组装过程是固定的,就拿手机来说,组装流水线

设计模式之建造者模式简明示例

建造者模式将一个复杂对象的创建和它的表示分离,使用同样的创建过程可以创建不同的表示 应用实例 在Java中,建造者模式是一种常见的设计模式,StringBuilder以及okhttp3.Request都应用了建造者模式: OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.baidu.com") .build(); 实现 我们将要实现一个Hu