15 Builder模式
- 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- 解读
- 构建过程:Director,指导装配过程,但是不负责每步具体的实现,由Builder对象具体实现每步,真正的指导者实现应该是有较为复杂的算法和运算过程,在运算过程中根据需要,才会调用生成器的方法来生成部件对象,并不仅仅是如同示例那样,简单的按照一定顺序调用生成器的方法来生成对象
- 表示:Product,不同的表示就是属性值不同的Product
- 就是将构建过程单独抽象成一个Director类,然后该过程中涉及到的具体实现由Builder完成,这样Director中不直接使用Product对象,完成了Director和Product的解耦,从而使同样的构建过程,只要配置不同的Builder实现,就可以创建不同的表示
- 本质:分离构建的过程(Director)和构建过程中具体的实现(Builder),从而使得构建算法可以重用
- 将构建过程和具体实现分离,实际上也就将构建过程和表示分离了
- 优点
- 松散耦合:生成器模式可以用同一个构建算法(Director),构建出表现上完全不同的产品(Product),实现产品构建和产品表现上的分离,从而使得构建算法可以复用,而具体产品表现也可以灵活的、方便的扩展和切换
- 可以很容易的改变产品的内部表示:由于Builder对象只是提供接口给Director使用,那么具体的部件创建和装配方式是被Builder接口隐藏了的,Director并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换Builder的具体实现即可,不用管Director,因此变得很容易
- 更好的复用性:生成器模式很好的实现了构建算法和具体产品实现的分离,这样一来,使得构建产品的算法(Director)可以复用。同样的道理,具体产品的实现(Builder)也可以复用,同一个产品的实现,可以配合不同的构建算法使用。
- 使用场景
- 如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时
- 如果同一个构建过程有着不同的表示时,例如effective java中的示例,构建过程都是Builder.build,但当Builder对象不同时,创建出的NutritionFactsBuilder的表示也不同
- 类图
15.1 代码
-
Builder
/** * 生成器接口,定义创建一个输出文件对象所需的各个部件的操作 */ public interface Builder { /** * 构建输出文件的Header部分 * @param ehm 文件头的内容 */ public void buildHeader(ExportHeaderModel ehm); /** * 构建输出文件的Body部分 * @param mapData 要输出的数据的内容 */ public void buildBody(Map<String,Collection<ExportDataModel>> mapData); /** * 构建输出文件的Footer部分 * @param efm 文件尾的内容 */ public void buildFooter(ExportFooterModel efm); }
-
TxtBuilder
/** * 实现导出数据到文本文件的的生成器对象 */ public class TxtBuilder implements Builder { /** * 用来记录构建的文件的内容,相当于产品 */ private StringBuffer buffer = new StringBuffer(); public void buildBody(Map<String, Collection<ExportDataModel>> mapData) { for(String tblName : mapData.keySet()){ //先拼接表名称 buffer.append(tblName+"\n"); //然后循环拼接具体数据 for(ExportDataModel edm : mapData.get(tblName)){ buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n"); } } } public void buildFooter(ExportFooterModel efm) { buffer.append(efm.getExportUser()); } public void buildHeader(ExportHeaderModel ehm) { buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n"); } public StringBuffer getResult(){ return buffer; } }
-
XmlBuilder
/** * 实现导出数据到XML文件的的生成器对象 */ public class XmlBuilder implements Builder { /** * 用来记录构建的文件的内容,相当于产品 */ private StringBuffer buffer = new StringBuffer(); public void buildBody(Map<String, Collection<ExportDataModel>> mapData){ buffer.append(" <Body>\n"); for(String tblName : mapData.keySet()){ //先拼接表名称 buffer.append(" <Datas TableName=\""+tblName+"\">\n"); //然后循环拼接具体数据 for(ExportDataModel edm : mapData.get(tblName)){ buffer.append(" <Data>\n"); buffer.append(" <ProductId>"+edm.getProductId()+"</ProductId>\n"); buffer.append(" <Price>"+edm.getPrice()+"</Price>\n"); buffer.append(" <Amount>"+edm.getAmount()+"</Amount>\n"); buffer.append(" </Data>\n"); } buffer.append(" </Datas>\n"); } buffer.append(" </Body>\n"); } public void buildFooter(ExportFooterModel efm) { buffer.append(" <Footer>\n"); buffer.append(" <ExportUser>"+efm.getExportUser()+"</ExportUser>\n"); buffer.append(" </Footer>\n"); buffer.append("</Report>\n"); } public void buildHeader(ExportHeaderModel ehm) { buffer.append("<?xml version='1.0' encoding='gb2312'?>\n"); buffer.append("<Report>\n"); buffer.append(" <Header>\n"); buffer.append(" <DepId>"+ehm.getDepId()+"</DepId>\n"); buffer.append(" <ExportDate>"+ehm.getExportDate()+"</ExportDate>\n"); buffer.append(" </Header>\n"); } public StringBuffer getResult(){ return buffer; } }
-
Director
/** * 指导者,指导使用生成器的接口来构建输出的文件的对象 */ public class Director { /** * 持有当前需要使用的生成器对象 */ private Builder builder; /** * 构造方法,传入生成器对象 * @param builder 生成器对象 */ public Director(Builder builder) { this.builder = builder; } /** * 指导生成器构建最终的输出的文件的对象 * @param ehm 文件头的内容 * @param mapData 数据的内容 * @param efm 文件尾的内容 */ public void construct(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm) { //1:先构建Header builder.buildHeader(ehm); //2:然后构建Body builder.buildBody(mapData); //3:然后构建Footer builder.buildFooter(efm); } }
-
Client
public class Client { public static void main(String[] args) { //准备测试数据 ExportHeaderModel ehm = new ExportHeaderModel(); ehm.setDepId("一分公司"); ehm.setExportDate("2010-05-18"); Map<String,Collection<ExportDataModel>> mapData = new HashMap<String,Collection<ExportDataModel>>(); Collection<ExportDataModel> col = new ArrayList<ExportDataModel>(); ExportDataModel edm1 = new ExportDataModel(); edm1.setProductId("产品001号"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2 = new ExportDataModel(); edm2.setProductId("产品002号"); edm2.setPrice(99); edm2.setAmount(55); //把数据组装起来 col.add(edm1); col.add(edm2); mapData.put("销售记录表", col); ExportFooterModel efm = new ExportFooterModel(); efm.setExportUser("张三"); //测试输出到文本文件 TxtBuilder txtBuilder = new TxtBuilder(); //创建指导者对象 Director director = new Director(txtBuilder); director.construct(ehm, mapData, efm); //把要输出的内容输出到控制台看看 System.out.println("输出到文本文件的内容:\n"+txtBuilder.getResult()); //测试输出到xml文件 XmlBuilder xmlBuilder = new XmlBuilder(); Director director2 = new Director(xmlBuilder); director2.construct(ehm, mapData, efm); //把要输出的内容输出到控制台看看 System.out.println("输出到XML文件的内容:\n"+xmlBuilder.getResult()); } }
15.3 effective java中的举例
-
客户端相当于充当了Director的角色,来指导构建器类去构建需要的复杂对象
-
NutritionFactsBuilder相当于Product,Builder相当于ConcreteBuilder
-
通过Builder的set方法可以得到一个新的Builder,使用新的Builder和老Builder创建处的Product的表现不同
-
为防止外界可以直接new NutritionFactsBuilder,所以将其构造器私有,且由于Builder只用于创建NutritionFactsBuilder,因此将Builder放在NutritionFactsBuilder内部
-
NutritionFactsBuilder
package chapter1.number2; //NutritionFactsBuilder为真正的产品,由于Builder只负责创建这一种产品,所以可以将Builder作为其内部类 public class NutritionFactsBuilder { //因为想构建不可变类,所以所有属性都设置为final private final int servingSize;//分量大小 必须 private final int servings;//使用次数 必须 private final int calories;//卡路里 可选 private final int fat;//脂肪 可选 private final int sodium;//纳 可选 private final int carbonhydrate;//碳水化合物可选 private NutritionFactsBuilder(Builder builder){ servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbonhydrate = builder.carbonhydrate; } @Override public String toString(){ return "NutritionFacts [servingSize="+servingSize+", servings="+servings+",calories="+calories+",fat="+fat+",sodium="+sodium+",carbonhydrate="+carbonhydrate; } public static class Builder{ private final int servingSize;//分量大小 必须 private final int servings;//使用次数 必须 private int calories = 0;//卡路里 可选 private int fat = 0;//脂肪 可选 private int sodium = 0;//纳 可选 private int carbonhydrate = 0;//碳水化合物可选 //2.构造函数中传入必填的成员 public Builder(int servingSize,int servings){ super(); this.servingSize = servingSize; this.servings = servings; } public Builder setCalories(int calories){ this.calories = calories; return this; } public Builder setFat(int fat){ this.fat = fat; return this; } public Builder setSodium(int sodium){ this.sodium = sodium; return this; } //相当于创造零件的buildPart1方法 public Builder setCarbonhydrate(int carbonhydrate){ this.carbonhydrate = carbonhydrate; return this; } //Builder中创建真正的对象的方法,相当于retrieveResult方法 public NutritionFactsBuilder build(){ return new NutritionFactsBuilder(this); } } }
-
Client
//客户端相当于充当了Director的角色,持有一个Builder对象,先通过set方法制造零件,最后调用build方法创建真正的产品 //同样的构建过程,传入不同的值时,创建出了含有不同属性值的对象 NutritionFactsBuilder cocaColaBuilder = new NutritionFactsBuilder.Builder(2500, 250).setCalories(1000).setFat(0) .setSodium(300).setCarbonhydrate(265).build(); System.out.println(cocaColaBuilder);