【学习难度:★★★★☆,使用频率:★★☆☆☆】
无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
在软件开发中,也存在大量类似汽车一样的复杂对象,它们拥有一系列成员属性,这些成员属性中有些是引用类型的成员对象。而且在这些复杂对象中,还可能存在一些限制条件,如某些属性没有赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等。
复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机。
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。
建造者模式包含如下角色:


在建造者模式的定义中提到了复杂对象,那么什么是复杂对象?简单来说,复杂对象是指那些包含多个成员属性的对象,这些成员属性也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件,一个典型的复杂对象类代码示例如下:
class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
在抽象建造者类中定义了产品的创建方法和返回方法,其典型代码如下:
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;
}
}
在抽象类Builder中声明了一系列抽象的buildPartX()方法用于创建复杂产品的各个部件,具体建造过程在ConcreteBuilder中实现,此外还提供了工厂方法getResult(),用于返回一个建造好的完整产品。
在ConcreteBuilder中实现了buildPartX()方法,通过调用Product的setPartX()方法可以给产品对象的成员属性设值。不同的具体建造者在实现buildPartX()方法时将有所区别,如setPartX()方法的参数可能不一样,在有些具体建造者类中某些setPartX()方法无须实现(提供一个空实现)。而这些对于客户端来说都无须关心,客户端只需知道具体建造者类型即可。
在建造者模式的结构中还引入了一个指挥者类Director,该类主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程,包括某个buildPartX()方法是否被调用以及多个buildPartX()方法调用的先后次序等。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。在实际生活中也存在类似指挥者一样的角色,如一个客户去购买电脑,电脑销售人员相当于指挥者,只要客户确定电脑的类型,电脑销售人员可以通知电脑组装人员给客户组装一台电脑。指挥者类的代码示例如下:
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
在指挥者类中可以注入一个抽象建造者类型的对象,其核心在于提供了一个建造方法construct(),在该方法中调用了builder对象的构造部件的方法,最后返回一个产品对象。
对于客户端而言,只需关心具体的建造者即可,一般情况下,客户端类代码片段如下所示:
Builder builder = new ConcreteBuilder(); //可通过配置文件实现
Director director = new Director(builder);
Product product = director.construct();
可以通过配置文件来存储具体建造者类ConcreteBuilder的类名,使得更换新的建造者时无须修改源代码,系统扩展更为方便。在客户端代码中,无须关心产品对象的具体组装过程,只需指定具体建造者的类型即可。
建造者模式与抽象工厂模式有点相似,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型并指导Director类如何去生成对象,侧重于一步步构造一个复杂对象,然后将结果返回。如果将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件,那么建造者模式就是一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车。
抽象建造者类中定义了产品的创建方法和返回方法;
建造者模式的结构中还引入了一个指挥者类Director,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象
在客户端代码中,无须关心产品对象的具体组装过程,只需确定具体建造者的类型即可,建造者模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。
实例一:KFC套餐
建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、 可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。
实例二:游戏角色的创建
Sunny公司开发人员决定使用建造者模式来实现游戏角色的创建,其基本结构如图8-3所示:
在图8-3中,ActorController充当指挥者,ActorBuilder充当抽象建造者,HeroBuilder、AngelBuilder和DevilBuilder充当具体建造者,Actor充当复杂产品。完整代码如下所示:
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:38
* @Description: Actor角色类:复杂产品,考虑到代码的可读性,只列出部分成员属性,
* 且成员属性的类型均为String,真实情况下,有些成员属性的类型需自定义
*/
public class Actor {
private String type;//角色类型
private String sex; //性别
private String face; //脸型
private String costume; //服装
private String hairstyle; //发型
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getFace() {
return face;
}
public void setFace(String face) {
this.face = face;
}
public String getCostume() {
return costume;
}
public void setCostume(String costume) {
this.costume = costume;
}
public String getHairstyle() {
return hairstyle;
}
public void setHairstyle(String hairstyle) {
this.hairstyle = hairstyle;
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:43
* @Description: 角色建造器:抽象建造者
*/
public abstract class ActorBuilder {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
//工厂方法,返回一个完整的游戏角色对象
public Actor createActor() {
return actor;
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:47
* @Description: 天使角色建造器:具体建造者
*/
public class AngelBuilder extends ActorBuilder{
@Override
public void buildType() {
actor.setType("天使");
}
@Override
public void buildSex() {
actor.setSex("女");
}
@Override
public void buildFace() {
actor.setFace("漂亮");
}
@Override
public void buildCostume() {
actor.setCostume("白裙");
}
@Override
public void buildHairstyle() {
actor.setHairstyle("披肩长发");
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:51
* @Description: 恶魔角色建造器:具体建造者
*/
public class DevilBuilder extends ActorBuilder {
@Override
public void buildType() {
actor.setType("恶魔");
}
@Override
public void buildSex() {
actor.setSex("妖");
}
@Override
public void buildFace() {
actor.setFace("丑陋");
}
@Override
public void buildCostume() {
actor.setCostume("黑衣");
}
@Override
public void buildHairstyle() {
actor.setHairstyle("光头");
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:45
* @Description: 英雄角色建造器:具体建造者
*/
public class HeroBuilder extends ActorBuilder{
@Override
public void buildType() {
actor.setType("英雄");
}
@Override
public void buildSex() {
actor.setSex("男");
}
@Override
public void buildFace() {
actor.setFace("英俊");
}
@Override
public void buildCostume() {
actor.setCostume("盔甲");
}
@Override
public void buildHairstyle() {
actor.setHairstyle("飘逸");
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 13:57
* @Description: 游戏角色创建控制器:指挥者
*/
public class ActorController {
//逐步构建复杂产品对象
public Actor construct(ActorBuilder ab) {
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
actor = ab.createActor();
return actor;
}
}
package com.zyz.demo1;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 16:29
* @Description:
*/
public class Client {
public static void main(String[] args) {
ActorBuilder ab; //针对抽象建造者编程
ab = new AngelBuilder();
ActorController ac = new ActorController();
Actor actor;
actor = ac.construct(ab); //通过指挥者创建完整的建造者对象
String type = actor.getType();;
System.out.println(type + "的外观");
System.out.println("性别:"+actor.getSex());
System.out.println("面容:"+actor.getFace());
System.out.println("服装:"+actor.getCostume());
System.out.println("发型:"+actor.getHairstyle());
}
}

在以下情况下可以使用建造者模式:
在很多游戏软件中,地图包括天空、地面、背景等组成部分,人物角色包括人体、服装、装备等组成部分,可以使用建造者模式对其进行设计,通过不同的具体建造者创建不同类型的地图或人物。
建造者模式的简化:
为了提高系统的灵活性和可扩展性,我们将具体建造者类的类名存储在配置文件中,并通过工具类XMLUtil来读取配置文件并反射生成对象,XMLUtil类的代码如下所示:
package com.zyz.demo1.config;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 16:55
* @Description: 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
*/
public class XMLUtil {
public static Object getBean() {
try {
String path = "F:\\java学习资料(后端)\\github管理后端学习资料\\后端学习\\设计模式\\代码\\DesignPatterns-Java-Examples\\4. 建造者模式\\src\\main\\resources\\config.xml";
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File(path));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c = Class.forName("com.zyz.demo1." + cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
配置文件config.xml中存储了具体建造者类的类名,代码如下所示:
<?xml version="1.0"?>
<config>
<className>HeroBuilder</className>
</config>
编写如下客户端测试代码:
package com.zyz.demo1;
import com.zyz.demo1.config.XMLUtil;
/**
* @author zyz
* @version 1.0
* @data 2023/5/10 16:58
* @Description:
*/
public class Client2 {
public static void main(String[] args) {
ActorBuilder ab; //针对抽象建造者编程
ab = (ActorBuilder) XMLUtil.getBean(); //反射生成具体建造者对象
ActorController ac = new ActorController();
Actor actor;
actor = ac.construct(ab); //通过指挥者创建完整的建造者对象
String type = actor.getType();;
System.out.println(type + "的外观");
System.out.println("性别:"+actor.getSex());
System.out.println("面容:"+actor.getFace());
System.out.println("服装:"+actor.getCostume());
System.out.println("发型:"+actor.getHairstyle());
}
}
执行结果
