设计模式学习笔记(十六)-生成器模式

概念:

  生成器模式也称之为建造者模式。生成器模式的意图在于将一个复杂的构建与其表示相分离,构建与产品分离。

UML:


    Ibuild接口清晰地反映了创建产品Product的流程。

生成器模式涉及4个关键角色:产品(Product),抽象生成器(IBuilder),具体生成器(Builder),指挥者(Director).

产品(Product): 所要构建的复杂对象。

抽象生成器(Builder): 抽象生成器是一个接口,该接口除了为创建一个Product对象的各个组件定义若干个方法外,还要定义返回Product对象的方法。

具体生成器(ConcreteBuilder): 实现Buidler接口的类。

指挥者(Director): 指挥者是一个类,该类需含有Builder接口申明的变量,指挥者的职责是负责向用户提供具体的生成器,利用具体生成器构造复杂的Product对象。


生成器模式示例代码

1、生成器接口定义的示例代码

[java] view plain copy
  1. /** 
  2.  * 生成器接口,定义创建一个产品对象所需的各个部件的操作 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public interface Builder {  
  7.   
  8.     /** 
  9.      * 示意方法,构建某个部件 
  10.      */  
  11.     public void buildPart();  
  12. }  

2、具体生成器实现的示例代码

[java] view plain copy
  1. /** 
  2.  * 具体的生成器实现对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class ConcreteBuilder implements Builder {  
  7.       
  8.     private Product resultProduct;  
  9.       
  10.     /** 
  11.      * 获取生成器最终构建的产品对象 
  12.      * @return 
  13.      */  
  14.     public Product getResultProduct() {  
  15.         return resultProduct;  
  16.     }  
  17.   
  18.     @Override  
  19.     public void buildPart() {  
  20.         //构建某个部件的功能处理  
  21.     }  
  22.   
  23. }  

3、相应的产品对象接口的示例代码

[java] view plain copy
  1. /** 
  2.  * 被构建的产品对象的接口 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public interface Product {  
  7.     //定义产品的操作  
  8. }  

4、最后是指导者的实现示意,示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 指导者,指导使用生成器的接口来构建产品对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class Director {  
  7.   
  8.     /** 
  9.      * 持有当前需要使用的生成器对象 
  10.      */  
  11.     private Builder builder;  
  12.   
  13.     /** 
  14.      * 构造方法,传人生成器对象 
  15.      * @param builder 
  16.      */  
  17.     public Director(Builder builder) {  
  18.         this.builder = builder;  
  19.     }  
  20.       
  21.     /** 
  22.      * 示意方法,指导生成器构建最终的产品对象 
  23.      */  
  24.     public void construct(){  
  25.         //通过使用生成器接口来构建最终的产品对象  
  26.         builder.buildPart();  
  27.     }  
  28. }  

应用场景-- 导出数据的应用框架

在讨论工厂方法模式的时候,提供了一个导出数据的应用框架。

对于导出数据的应用框架,通常在导出数据上,会有一些约束的方式,比如导出成文本格式、数据库备份形式、Excel格式、Xml格式等。

在工厂方法模式章节里面,讨论并使用工厂方法模式来解决了如何选择具体导出方式的问题,并没有涉及到每种方式具体如何实现。

换句话说,在讨论工厂方法模式的时候,并没有讨论如何实现导出成文本、Xml等具体格式,本章就来讨论这个问题。

对于导出数据的应用框架,通常对于具体的导出内容和格式是有要求的,加入现在有如下要求,简单描述一下:

  • 导出的文件,不管是什么格式,都分成3个部分,分别是文件头、文件体、文件尾。
  • 在文件头部分,需要描述如下信息:分公司或者门市编号、导出数据的日期。
  • 在文件体部分,需要描述如下信息:表名称,然后分条描述数据。
  • 在文件尾部分,需要描述如下信息:输出人。


1、下面将描述文件各个部分的数据对象定义出来

描述输出到文件头的内容的对象,示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 描述输出到文件头的内容的对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class ExportHeaderModel {  
  7.   
  8.     /** 
  9.      * 分公司或者门市编号 
  10.      */  
  11.     private String depId;  
  12.     /** 
  13.      * 导出数据的日期 
  14.      */  
  15.     private String exportDate;  
  16.       
  17.     public String getDepId() {  
  18.         return depId;  
  19.     }  
  20.     public void setDepId(String depId) {  
  21.         this.depId = depId;  
  22.     }  
  23.     public String getExportDate() {  
  24.         return exportDate;  
  25.     }  
  26.     public void setExportDate(String exportDate) {  
  27.         this.exportDate = exportDate;  
  28.     }  
  29.       
  30. }  

描述输出数据的对象,示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 描述输出数据的对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class ExportDataModel {  
  7.   
  8.     /** 
  9.      * 产品编号 
  10.      */  
  11.     private String productId;  
  12.     /** 
  13.      * 销售价格 
  14.      */  
  15.     private double price;  
  16.     /** 
  17.      * 销售数量 
  18.      */  
  19.     private double amount;  
  20.       
  21.     public String getProductId() {  
  22.         return productId;  
  23.     }  
  24.     public void setProductId(String productId) {  
  25.         this.productId = productId;  
  26.     }  
  27.     public double getPrice() {  
  28.         return price;  
  29.     }  
  30.     public void setPrice(double price) {  
  31.         this.price = price;  
  32.     }  
  33.     public double getAmount() {  
  34.         return amount;  
  35.     }  
  36.     public void setAmount(double amount) {  
  37.         this.amount = amount;  
  38.     }  
  39.       
  40. }  

描述输出到文件尾的内容的对象,示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 描述输出到文件尾的内容的对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class ExportFooterModel {  
  7.   
  8.     /** 
  9.      * 输出人 
  10.      */  
  11.     private String exportUser;  
  12.   
  13.     public String getExportUser() {  
  14.         return exportUser;  
  15.     }  
  16.   
  17.     public void setExportUser(String exportUser) {  
  18.         this.exportUser = exportUser;  
  19.     }  
  20.       
  21. }  

2、定义Builder接口,主要是把导出各种格式文件的处理过程的步骤定义出来,每个步骤负责构建最终导出文件的一部分。示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 生成器接口,定义创建一个输出文件对象所需的各个部件的操作 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public interface Builder {  
  7.   
  8.     /** 
  9.      * 构建输出文件的Header部分 
  10.      * @param ehm 
  11.      */  
  12.     public void buildHeader(ExportHeaderModel ehm);  
  13.       
  14.     /** 
  15.      * 构建输出文件的Body部分 
  16.      * @param mapData 
  17.      */  
  18.     public void buildBody(Map<String,List<ExportDataModel>> mapData);  
  19.       
  20.     /** 
  21.      * 构建输出文件的Footer部分 
  22.      * @param efm 
  23.      */  
  24.     public void buildFooter(ExportFooterModel efm);  
  25. }  

3、具体的生成器实现。

导出到文本文件的的生成器实现。示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 实现导出文件到文本文件的生成器对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class TxtBuilder implements Builder {  
  7.   
  8.     /** 
  9.      * 用来记录构建的文件的内容,相当于产品 
  10.      */  
  11.     private StringBuffer buffer = new StringBuffer();  
  12.       
  13.     @Override  
  14.     public void buildHeader(ExportHeaderModel ehm) {  
  15.         buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");  
  16.     }  
  17.   
  18.     @Override  
  19.     public void buildBody(Map<String, List<ExportDataModel>> mapData) {  
  20.         for(String tablName : mapData.keySet()){  
  21.               
  22.             //先拼接表名  
  23.             buffer.append(tablName+"\n");  
  24.             //然后循环拼接具体数据  
  25.             for(ExportDataModel edm : mapData.get(tablName)){  
  26.                 buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     @Override  
  32.     public void buildFooter(ExportFooterModel efm) {  
  33.         buffer.append(efm.getExportUser());  
  34.     }  
  35.       
  36.     public StringBuffer getResult(){  
  37.         return buffer;  
  38.     }  
  39.   
  40. }  

导出到Xml文件的的生成器实现。示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 实现导出文件到Xml文件的生成器对象 
  3.  * @author FX_SKY 
  4.  * 
  5.  */  
  6. public class XmlBuilder implements Builder {  
  7.   
  8.     /** 
  9.      * 用来记录构建的文件的内容,相当于产品 
  10.      */  
  11.     private StringBuffer buffer = new StringBuffer();  
  12.       
  13.     @Override  
  14.     public void buildHeader(ExportHeaderModel ehm) {  
  15.         buffer.append("<?xml version='1.0' encoding='UTF-8'?>\n");  
  16.         buffer.append("<Report>\n");  
  17.         buffer.append("\t<Header>\n");  
  18.         buffer.append("\t\t<DepId>"+ehm.getDepId()+"</DepId>\n");  
  19.         buffer.append("\t\t<ExportDate>"+ehm.getExportDate()+"</ExportDate>\n");  
  20.           
  21.         buffer.append("\t</Header>\n");  
  22.     }  
  23.   
  24.     @Override  
  25.     public void buildBody(Map<String, List<ExportDataModel>> mapData) {  
  26.         buffer.append("\t<Body>\n");  
  27.         for(String tablName : mapData.keySet()){  
  28.             //先拼接表名  
  29.             buffer.append("\t\t<Datas TableName=\""+tablName+"\">\n");  
  30.             //然后循环拼接具体数据  
  31.             for(ExportDataModel edm : mapData.get(tablName)){  
  32.                   
  33.                 buffer.append("\t\t\t<Data>\n");  
  34.                   
  35.                 buffer.append("\t\t\t\t<ProductId>"+edm.getProductId()+"</ProductId>\n");  
  36.                 buffer.append("\t\t\t\t<Price>"+edm.getPrice()+"</Price>\n");  
  37.                 buffer.append("\t\t\t\t<Amount>"+edm.getAmount()+"</Amount>\n");  
  38.                   
  39.                 buffer.append("\t\t\t</Data>\n");  
  40.             }  
  41.               
  42.             buffer.append("\t\t</Datas>\n");  
  43.         }  
  44.         buffer.append("\t</Body>\n");  
  45.     }  
  46.   
  47.     @Override  
  48.     public void buildFooter(ExportFooterModel efm) {  
  49.         buffer.append("\t<Footer>\n");  
  50.         buffer.append("\t\t<ExportUser>"+efm.getExportUser()+"</ExportUser>\n");  
  51.         buffer.append("\t</Footer>\n");  
  52.         buffer.append("</Report>\n");  
  53.     }  
  54.       
  55.     public StringBuffer getResult(){  
  56.         return buffer;  
  57.     }  
  58.   
  59. }  

4、指导者。有了具体的生成器实现后,需要由指导者来指导它进行具体的产品构建。示例代码如下:

[java] view plain copy
  1. /** 
  2.  * 指导者,指导使用生成器的接口来构建输出的文件对象 
  3.  *  
  4.  * @author FX_SKY 
  5.  *  
  6.  */  
  7. public class Director {  
  8.   
  9.     /** 
  10.      * 持有当前需要的使用的生成器对象 
  11.      */  
  12.     private Builder builder;  
  13.   
  14.     /** 
  15.      * 构造方法,传入生成器对象 
  16.      *  
  17.      * @param builder 
  18.      */  
  19.     public Director(Builder builder) {  
  20.         this.builder = builder;  
  21.     }  
  22.   
  23.     public void construct(ExportHeaderModel ehm,  
  24.             Map<String, List<ExportDataModel>> mapData, ExportFooterModel efm) {  
  25.   
  26.         //1.先构建Header  
  27.         builder.buildHeader(ehm);  
  28.           
  29.         //2.然后构建Body  
  30.         builder.buildBody(mapData);  
  31.           
  32.         //3.再构建Footer  
  33.         builder.buildFooter(efm);  
  34.     }  
  35. }  

5、客户端测试代码如下:

[java] view plain copy
  1. public class Client {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.           
  8.         //准备测试数据  
  9.         ExportHeaderModel ehm = new ExportHeaderModel();  
  10.         ehm.setDepId("一分公司");  
  11.         ehm.setExportDate("2010-05-18");  
  12.           
  13.         Map<String, List<ExportDataModel>> mapData = new HashMap<String, List<ExportDataModel>>();  
  14.         List<ExportDataModel> col = new ArrayList<ExportDataModel>();  
  15.           
  16.         ExportDataModel edm1 = new ExportDataModel();  
  17.         edm1.setProductId("产品001号");  
  18.         edm1.setPrice(100);  
  19.         edm1.setAmount(80);  
  20.           
  21.         ExportDataModel edm2 = new ExportDataModel();  
  22.         edm2.setProductId("产品002号");  
  23.         edm2.setPrice(120);  
  24.         edm2.setAmount(280);  
  25.           
  26.         ExportDataModel edm3 = new ExportDataModel();  
  27.         edm3.setProductId("产品003号");  
  28.         edm3.setPrice(320);  
  29.         edm3.setAmount(380);  
  30.           
  31.         col.add(edm1);  
  32.         col.add(edm2);  
  33.         col.add(edm3);  
  34.           
  35.         mapData.put("销售记录表", col);  
  36.           
  37.         ExportFooterModel efm = new ExportFooterModel();  
  38.         efm.setExportUser("张三");  
  39.           
  40.         //测试输出到文本文件  
  41.         TxtBuilder txtBuilder = new TxtBuilder();  
  42.         //创建指导者对象  
  43.         Director director = new Director(txtBuilder);  
  44.         director.construct(ehm, mapData, efm);  
  45.           
  46.         //把要输出的内容输出到控制台看看  
  47.         System.out.println("输出到文本文件的内容:"+txtBuilder.getResult().toString());  
  48.           
  49.         XmlBuilder xmlBuilder = new XmlBuilder();  
  50.         Director director2 = new Director(xmlBuilder);  
  51.         director2.construct(ehm, mapData, efm);  
  52.           
  53.         //把要输出的内容输出到控制台看看  
  54.         System.out.println("输出到Xml文件的内容:"+xmlBuilder.getResult().toString());  
  55.     }  
  56.   
  57. }  

生成器模式的功能

生成器模式的主要功能是构建复杂的产品,而且是细化的,分步骤的构建产品,也就是生成器模式重在一步一步解决构造复杂对象的问题。如果仅仅这么认知生成器模式的功能是不够的。

更为重要的是,这个构建的过程是统一的、固定不变的,变化的部分放到生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。


使用生成器模式构建复杂的对象

考虑这样的一个实际应用,Android图片异步加载框架,需要要创建图片加载配置的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如,线程池的数量不能小于2个、内存图片缓存的大小不能为负值等等。


要想简洁直观、安全性好,有具有很好的扩展性地创建这个对象的话,一个较好的选择就是使用Builder模式,把复杂的创建过程通过Builder来实现。

采用Builder模式来构建复杂的对象,通常会对Builder模式进行一定的简化,因为目标明确,就是创建某个复杂对象,因此做适当简化会使程序更简洁。大致简化如下:

  • 由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的构建器类就可以了。
  • 对于创建一个负责的对象,可能会有很多种不同的选择和步骤,干脆去掉“指导者”,把指导者的功能和Client的功能合并起来,也就是说,Client就相当于指导者,它来指导构建器类去构建需要的复杂对象。


[java] view plain copy
  1. public final class ImageLoaderConfiguration {  
  2.   
  3.     final Executor taskExecutor;  
  4.   
  5.     final int memoryCacheSize;  
  6.       
  7.     final int threadPoolSize;  
  8.     final int threadPriority;  
  9.   
  10.     final boolean writeLogs;  
  11.   
  12.     private ImageLoaderConfiguration(final Builder builder) {  
  13.         taskExecutor = builder.taskExecutor;  
  14.         threadPoolSize = builder.threadPoolSize;  
  15.         threadPriority = builder.threadPriority;  
  16.         memoryCacheSize = builder.memoryCacheSize;  
  17.         writeLogs = builder.writeLogs;  
  18.     }  
  19.   
  20.     /** 
  21.      * Builder for {@link ImageLoaderConfiguration} 
  22.      * 
  23.      * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) 
  24.      */  
  25.     public static class Builder {  
  26.   
  27.         public static final int DEFAULT_THREAD_POOL_SIZE = 3;  
  28.           
  29.         public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1;  
  30.   
  31.         private int memoryCacheSize = 0;  
  32.           
  33.         private Executor taskExecutor = null;  
  34.   
  35.         private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;  
  36.         private int threadPriority = DEFAULT_THREAD_PRIORITY;  
  37.   
  38.         private boolean writeLogs = false;  
  39.   
  40.         public Builder() {  
  41.         }  
  42.   
  43.         public Builder taskExecutor(Executor executor) {  
  44.             if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY) {  
  45.               
  46.             }  
  47.   
  48.             this.taskExecutor = executor;  
  49.             return this;  
  50.         }  
  51.   
  52.         public Builder threadPoolSize(int threadPoolSize) {  
  53.   
  54.             this.threadPoolSize = threadPoolSize;  
  55.             return this;  
  56.         }  
  57.   
  58.         public Builder threadPriority(int threadPriority) {  
  59.   
  60.             if (threadPriority < Thread.MIN_PRIORITY) {  
  61.                 this.threadPriority = Thread.MIN_PRIORITY;  
  62.             } else {  
  63.                 if (threadPriority > Thread.MAX_PRIORITY) {  
  64.                     this.threadPriority = Thread.MAX_PRIORITY;  
  65.                 } else {  
  66.                     this.threadPriority = threadPriority;  
  67.                 }  
  68.             }  
  69.             return this;  
  70.         }  
  71.   
  72.         public Builder memoryCacheSize(int memoryCacheSize) {  
  73.             if (memoryCacheSize <= 0throw new IllegalArgumentException("memoryCacheSize must be a positive number");  
  74.   
  75.             this.memoryCacheSize = memoryCacheSize;  
  76.             return this;  
  77.         }  
  78.   
  79.         public Builder writeDebugLogs() {  
  80.             this.writeLogs = true;  
  81.             return this;  
  82.         }  
  83.   
  84.         /** Builds configured {@link ImageLoaderConfiguration} object */  
  85.         public ImageLoaderConfiguration build() {  
  86.             initEmptyFieldsWithDefaultValues();  
  87.             return new ImageLoaderConfiguration(this);  
  88.         }  
  89.   
  90.         private void initEmptyFieldsWithDefaultValues() {  
  91.             if (taskExecutor == null) {  
  92.                   
  93.             }  
  94.         }  
  95.     }  
  96. }  
客户端调用示例代码如下:

[java] view plain copy
  1. public class Client {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.           
  8.         ImageLoaderConfiguration  config = new ImageLoaderConfiguration.Builder()  
  9.         .taskExecutor(Executors.newCachedThreadPool())  
  10.         .threadPoolSize(3)  
  11.         .threadPriority(Thread.MIN_PRIORITY + 3)  
  12.         .memoryCacheSize(1024*16)  
  13.         .build();  
  14.     }  
  15.   
  16. }  

生成器模式的优点

松散耦合

生成器模式可以用同一个构建算法构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。生成器模式正是把产品构建的过程独立出来,使它和具体产品的表现分松散耦合,从而使得构建算法可以复用,而具体产品表现也可以很灵活地、方便地扩展和切换。

可以很容易的改变产品的内部表示

在生成器模式中,由于Builder对象只是提供接口给Director使用,那么具体部件创建和装配方式是被Builder接口隐藏了的,Director并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换Builder接口的具体实现即可,不用管Director,因此变得很容易。

更好的复用性

生成器模式很好的实现构建算法和具体产品实现的分离。这样一来,使得构建产品的算法可以复用。同样的道理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用。


生成器模式的本质:分离整体构建算法和部件构造。

虽然在生成器模式的整体构建算法中,会一步一步引导Builder来构建对象,但这并不是说生成器主要就是用来实现分步骤构建对象的。生成器模式的重心还是在于分离整体构建算法和部件构造,而分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物。


何时选用生成器模式

建议在以下情况中选用生成器模式。

  • 如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
  • 如果同一个构建过程有着不同的表示时。

阅读更多
个人分类: 【设计模式】
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭