ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 引入 在阎宏博士的《JAVA与模式》一书中开头是这样描述建造(Builder)模式的: > 建造模式是对象的创建模式。建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。 **产品的内部表象**   一个产品常有不同的组成成分作为产品的零件,这些零件有可能是对象,也有可能不是对象,它们通常又叫做产品的内部表象(internal representation)。不同的产品可以有不同的内部表象,也就是不同的零件。使用建造模式可以使客户端不需要知道所生成的产品有哪些零件,每个产品的对应零件彼此有何不同,是怎么建造出来的,以及怎么组成产品。 **对象性质的建造**   有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,而在最起码的收件人地址得到赋值之前,这个电子邮件不能发送。   有些情况下,一个对象的一些性质必须按照某个顺序赋值才有意义。在某个性质没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复杂的商业逻辑。这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零件,建造产品的过程是建造零件的过程。由于建造零件的过程很复杂,因此,这些零件的建造过程往往被“外部化”到另一个称做建造者的对象里,建造者对象返还给客户端的是一个全部零件都建造完毕的产品对象。   建造模式利用一个导演者对象和具体建造者对象一个个地建造出所有的零件,从而建造出完整的产品对象。建造者模式将产品的结构和产品的零件的建造过程对客户端隐藏起来,把对建造过程进行指挥的责任和具体建造者零件的责任分割开来,达到责任划分和封装的目的。  ## 模式结构 建造者模式的UML结构图: ![](https://box.kancloud.cn/08a60e709eaca1c5a47255403dc1c321_751x313.png) 四个角色,它们分别是: *   抽象建造者(Builder)角色:给 出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者 (ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的两种方法:一种是建造方法(buildPart1和 buildPart2),另一种是返还结构方法(retrieveResult)。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少 零件,就有多少相应的建造方法。 *   具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序调用下创建产品的实例。这个角色要完成的任务包括:1.实现抽象建造者Builder所声明的接口,给出一步一步地完成创建产品实例的操作。2.在建造过程完成后,提供产品的实例。 *   导演者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。应当指出的是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。 *   产品(Product)角色:产品便是建造中的复杂对象。一般来说,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以是不相关联的。 ## 代码实现 导演者角色是与客户端打交道的角色。导演者将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但是却不为客户端所知。   一般来说,每有一个产品类,就有一个相应的具体建造者类。这些产品应当有一样数目的零件,而每有一个零件就相应地在所有的建造者角色里有一个建造方法。 KFC里面一般都有好几种可供客户选择的套餐,它可以根据客户所点的套餐,然后在后面做这些套餐,返回给客户的事一个完整的、美好的套餐。下面我们将会模拟这个过程,我们约定套餐主要包含汉堡、薯条、可乐、鸡腿等等组成部分,使用不同的组成部分就可以构建出不同的套餐。 ![](https://box.kancloud.cn/9e3f443fdaa71e68736bfb547674765f_813x445.png) 首先是套餐类:Meal.java ~~~ public class Meal { private String food; private String drink; public String getFood() { return food; } public void setFood(String food) { this.food = food; } public String getDrink() { return drink; } public void setDrink(String drink) { this.drink = drink; } } ~~~ 套餐构造器:MealBuilder.java ~~~ public abstract class MealBuilder { Meal meal = new Meal(); public abstract void buildFood(); public abstract void buildDrink(); public Meal getMeal(){ return meal; } } ~~~ 然后是套餐A、套餐B。这个两个套餐都是实现抽象套餐类。 ~~~ public class MealA extends MealBuilder{ public void buildDrink() { meal.setDrink("一杯可乐"); } public void buildFood() { meal.setFood("一盒薯条"); } } ~~~ ~~~ public class MealB extends MealBuilder{ public void buildDrink() { meal.setDrink("一杯柠檬果汁"); } public void buildFood() { meal.setFood("三个鸡翅"); } } ~~~ 最后是KFC的服务员,它相当于一个指挥者,它决定了套餐是的实现过程,然后给你一个完美的套餐。 ~~~ public class KFCWaiter { private MealBuilder mealBuilder; public void setMealBuilder(MealBuilder mealBuilder) { this.mealBuilder = mealBuilder; } public Meal construct(){ //准备食物 mealBuilder.buildFood(); //准备饮料 mealBuilder.buildDrink(); //准备完毕,返回一个完整的套餐给客户 return mealBuilder.getMeal(); } } ~~~ 测试类 ~~~ public class Client { public static void main(String[] args) { //服务员 KFCWaiter waiter = new KFCWaiter(); //套餐A MealA a = new MealA(); //服务员准备套餐A waiter.setMealBuilder(a); //获得套餐 Meal mealA = waiter.construct(); System.out.print("套餐A的组成部分:"); System.out.println(mealA.getFood()+"---"+mealA.getDrink()); } } ~~~ **使用建造模式构建复杂对象** 建造模式分成两个很重要的部分:   1. 一个部分是Builder接口,这里是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;   2. 另外一个部分是Director,Director是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤地来执行。   不管如何变化,建造模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在建造模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式。   再直白点说,建造模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。 考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。这个对象里有很多类似这样的约束,采用建造模式来构建复杂的对象,通常会对建造模式进行一定的简化,因为目标明确,就是创建某个复杂对象,因此做适当简化会使程序更简洁。大致简化如下:   ●  由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。   ●  对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“导演者”,把导演者的功能和Client的功能合并起来,也就是说,Client这个时候就相当于导演者,它来指导构建器类去构建需要的复杂对象。 保险合同类 ~~~ /** * 保险合同对象 */ public class InsuranceContract { //保险合同编号   private String contractId; /** * 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订 * 也就是说,“被保险人员”和“被保险公司”这两个属性,不可能同时有值 */ private String personName; //被保险公司的名称   private String companyName; //保险开始生效日期   private long beginDate; //保险失效日期,一定会大于保险开始生效日期   private long endDate; //其他数据   private String otherData; //私有构造方法   private InsuranceContract(ConcreteBuilder builder){ this.contractId = builder.contractId; this.personName = builder.personName; this.companyName = builder.companyName; this.beginDate = builder.beginDate; this.endDate = builder.endDate; this.otherData = builder.otherData; } /** * 保险合同的一些操作 */ public void someOperation(){ System.out.println("当前正在操作的保险合同编号为【"+this.contractId+"】"); } public static class ConcreteBuilder{ private String contractId; private String personName; private String companyName; private long beginDate; private long endDate; private String otherData; /** * 构造方法,传入必须要有的参数 * @param contractId 保险合同编号 * @param beginDate 保险合同开始生效日期 * @param endDate 保险合同失效日期 */ public ConcreteBuilder(String contractId,long beginDate,long endDate){ this.contractId = contractId; this.beginDate = beginDate; this.endDate = endDate; } //被保险人员的名称     public ConcreteBuilder setPersonName(String personName) { this.personName = personName; return this; } //被保险公司的名称     public ConcreteBuilder setCompanyName(String companyName) { this.companyName = companyName; return this; } //其他数据     public ConcreteBuilder setOtherData(String otherData) { this.otherData = otherData; return this; } /** * 构建真正的对象并返回 * @return 构建的保险合同对象 */ public InsuranceContract build(){ if(contractId == null || contractId.trim().length()==0){ throw new IllegalArgumentException("合同编号不能为空"); } boolean signPerson = (personName != null && personName.trim().length() > 0); boolean signCompany = (companyName != null && companyName.trim().length() > 0); if(signPerson && signCompany){ throw new IllegalArgumentException("一份保险合同不能同时与个人和公司签订"); } if(signPerson == false && signCompany == false){ throw new IllegalArgumentException("一份保险合同不能没有签订对象"); } if(beginDate <= 0 ){ throw new IllegalArgumentException("一份保险合同必须有开始生效的日期"); } if(endDate <=0){ throw new IllegalArgumentException("一份保险合同必须有失效的日期"); } if(endDate < beginDate){ throw new IllegalArgumentException("一份保险合同的失效日期必须大于生效日期"); } return new InsuranceContract(this); } } } ~~~ 客户端类 ~~~ public class Client { public static void main(String[]args){ //创建构建器对象     InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("9527", 123L, 456L); //设置需要的数据,然后构建保险合同对象     InsuranceContract contract = builder.setPersonName("小明").setOtherData("test").build(); //操作保险合同对象的方法     contract.someOperation(); } } ~~~ 在本例中将具体建造者合并到了产品对象中,并将产品对象的构造函数私有化,防止客户端不使用构建器来构建产品对象,而是直接去使用new来构建产品对象所导致的问题。另外,这个构建器的功能就是为了创建被构建的对象,完全可以不用单独一个类。 ## 优点 * 1、将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,使得我们能够更加精确的控制复杂对象的产生过程。 * 2、将产品的创建过程与产品本身分离开来,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。 * 3、每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。 ## 缺点 * 1、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。 * 2、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 ## 建造者模式和抽象工厂模式的区别 在抽象工厂样式中,每一次工厂对象被呼叫时都会传回一个**完整的产品对象**,而使用端有可能会决定把这些产品组装成一个更大的和复杂的产品,也有可能不会。工厂对象是没有状态的,不知道上一次构建的是哪一个产品,也没有未来的概念,不知道下一次构建的是哪一个产品,更不知道自己构建的产品在更高层的产品结构蓝图中是什么位置。 建造类别则不同,建造样式的重点在导演者角色。**导演者对象是有状态的**,它知道整体蓝图,知道上一次、这一次和下一次交给建造者角色去构建的零件是什么,以便能够将这些零件组装成为一个更大规模的产品。它一点一点地建造出一个复杂的产品,而这个产品的组装程序就发生在导演者角色内部。建造者样式的使用端拿到的是一个完整的最后产品。 换言之,虽然抽象工厂样式与建造样式都是设计样式,但是抽象工厂样式处在更加具体的尺度上,而建造样式则处于更加宏观的尺度上。一个系统可以由一个建造样式和一个抽象工厂样式组成,使用端通过呼叫这个导演角色,间接地呼叫另一个抽象工厂样式的工厂角色。工厂样式传回不同产品族的零件,而建造者样式则把它们组装起来。 比如仍以众神造人为例,女娲利用建造样式负责把灵魂、耳目、手臂等组合成一个完整的人,而黄帝、上骈、桑林各自利用工厂样式创造出灵魂、耳目、臂手等。女娲不必考虑灵魂、耳目、手臂是什么样子、怎么创造出来的,这就成为一个由建造样式和抽象工厂样式组合而成的系统。 ## 适用场景 * 1、需要生成的产品对象有复杂的内部结构,每一个内部成分本身可以是对象,也可以仅仅是一个对象(即产品对象)的一个组成部分。 * 2、需要生成的产品对象的属性相互依赖。建造模式可以强制实行一种分步骤进行的建造过程,因此,如果产品对象的一个属性必须在另一个属性被赋值之后才可以被赋值,使用建造模式是一个很好的设计思想。 * 3、在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易得到。 ## 总结 * 1、建造者模式是将一个复杂对象的创建过程给封装起来,客户只需要知道可以利用对象名或者类型就能够得到一个完整的对象实例,而不需要关心对象的具体创建过程。 * 2、建造者模式将对象的创建过程与对象本身隔离开了,使得细节依赖于抽象,符合依赖倒置原则。可以使用相同的创建过程来创建不同的产品对象。