# 建造者模式(Builder)(生成器)

建造者模式详解
建造者模式详解 2

# 思考

# 与工厂模式有何区别?

  1. 建造者模式是让建造者类来负责对象的创建工作。上面讲到的工厂模式,是由工厂类来负责对象创建的工作。那它们之间有什么区别呢?
  2. 实际上,工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化” 地创建不同的对象

网上有一个经典的例子很好地解释了两者的区别:

顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。

# 应用场景

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

# 本质

  • 用户只需要给出指定复杂对象的类型和内容;
  • 建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

# 解决的问题

  1. 降低创建复杂对象的复杂度
  2. 隔离了创建对象的构建过程 & 表示
    从而:

方便用户创建复杂的对象(不需要知道实现过程)
代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)

# 结构

  1. 生成器的接口定义
/**
   * 生成器接口,定义创建一个产品对象所需的各个部件的操作
   */
public interface Builder {
    /**
     * 示意方法,构建某个部件
     */
    public void buildPart();
}
  1. 再看看具体的生成器实现,示例代码如下:
/**
   * 具体的生成器实现对象
   */
public class ConcreteBuilder implements Builder {
    /**
     * 生成器最终构建的产品对象
     */
    private Product resultProduct;
    /**
     * 获取生成器最终构建的产品对象
     * @return 生成器最终构建的产品对象
     */
    public Product getResult() {
       return resultProduct;
    }
    public void buildPart() {
       // 构建某个部件的功能处理
    }
}
  1. 看看相应的产品对象的接口示意,示例代码如下:
/**
   * 被构建的产品对象的接口
   */
public interface Product {
    // 定义产品的操作
}
  1. 再来看看指导者的实现示意,示例代码如下:
/**
   * 指导者,指导使用生成器的接口来构建产品的对象
   */
public class Director {
    /**
     * 持有当前需要使用的生成器对象
     */
    private Builder builder;
    /**
     * 构造方法,传入生成器对象
     * @param builder 生成器对象
     */
    public Director(Builder builder) {
       this.builder = builder;
    }
    /**
     * 示意方法,指导生成器构建最终的产品对象
     */
    public void construct() {
       // 通过使用生成器接口来构建最终的产品对象
       builder.buildPart();
    }
}

# 实例

先来看看定义的 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);
}

接下来看看具体的生成器实现,其实就是把原来示例中,写在一起的实现,分拆成多个步骤实现了,先看看导出数据到文本文件的生成器实现,示例代码如下:

/**
   * 实现导出数据到文本文件的的生成器对象
   */
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;
    }   
}

再看看导出数据到 XML 文件的生成器实现,示例代码如下:

/**
   * 实现导出数据到 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;
    }
}

指导者
有了具体的生成器实现后,需要有指导者来指导它进行具体的产品构建,由于构建的产品是文本内容,所以就不用单独定义产品对象了。示例代码如下:

/**
   * 指导者,指导使用生成器的接口来构建输出的文件的对象
   */
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);
    }
}

都实现得差不多了,该来写个客户端好好测试一下了。示例代码如下:

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());
    }
}

# 原型模式 (Prototype)

原型模式 (Prototype)

# 总结

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。

原型模式主要是拥有一个实例,然后克隆一样的属性,创建出一个新的实例,整理还是非常简单的

# 应用场景

建议在如下情况中,选用原型模式:

如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到;

如果需要实例化的类是在运行时刻动态指定时,可以使用原型模式,通过克隆原型来得到需要的实例;

# 本质

原型模式的本质:克隆生成对象。

克隆是手段,目的还是生成新的对象实例。正是因为原型的目的是为了生成新的对象实例,原型模式通常是被归类为创建型的模式

原型模式也可以用来解决 “只知接口而不知实现的问题”,使用原型模式,可以出现一种独特的 “接口造接口” 的景象,这在面向接口编程中很有用。同样的功能也可以考虑使用工厂来实现。

另外,原型模式的重心还是在创建新的对象实例,至于创建出来的对象,其属性的值是否一定要和原型对象属性的值完全一样,这个并没有强制规定,只不过在目前大多数实现中,克隆出来的对象和原型对象的属性值是一样的。

也就是说,可以通过克隆来创造值不一样的实例,但是对象类型必须一样。可以有部分甚至是全部的属性的值不一样,可以有选择性的克隆,就当是标准原型模式的一个变形使用吧。

# 和抽象工厂的区别

  1. 原型模式和抽象工厂模式

功能上有些相似,都是用来获取一个新的对象实例的。

不同之处在于,原型模式的着眼点是在如何创造出实例对象来,最后选择的方案是通过克隆;而抽象工厂模式的着眼点则在于如何来创造产品簇,至于具体如何创建出产品簇中的每个对象实例,抽象工厂模式不是很关注

正是因为它们的关注点不一样,所以它们也可以配合使用,比如在抽象工厂模式里面,具体创建每一种产品的时候就可以使用该种产品的原型,也就是抽象工厂管产品簇,具体的每种产品怎么创建则可以选择原型模式

  1. 原型模式和生成器模式

这两种模式可以配合使用。

生成器模式关注的是构建的过程,而在构建的过程中,很可能需要某个部件的实例,那么很自然地就可以应用上原型模式,通过原型模式来得到部件的实例。

# 实例

public interface OrderApi {
     int getOrderProductNum();
     void setOrderProductNum(int num);
}
public class PersonalOrder implements OrderApi, Cloneable {
    private String customerName;
    private String productId;
    private int orderProductNum = 0;
    public String getCustomerName() {
        return customerName;
    }
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    @Override
    public void setOrderProductNum(int orderProductNum) {
        this.orderProductNum = orderProductNum;
    }
    public String getProductId() {
        return productId;
    }
    @Override
    public String toString() {
        return "本个人订单的订购人是=" + this.customerName + ",订购产品是=" + this.productId + ",订购数量为=" + this.orderProductNum;
    }
    public void setProductId(String productId) {
        this.productId = productId;
    }
    @Override
    public int getOrderProductNum() {
        return this.orderProductNum;
    }
    @Override
    // 克隆方法的真正实现,直接调用父类的克隆方法就可以了
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class main {
    public static void main(String[] args) throws CloneNotSupportedException {
        PersonalOrder personalOrder = new PersonalOrder();
        personalOrder.setOrderProductNum(100);
        System.out.println("这是第一次获取的对象实例===" + personalOrder.getOrderProductNum());
        PersonalOrder oa2 = (PersonalOrder) personalOrder.clone();
        oa2.setOrderProductNum(80);
        System.out.println("输出克隆出来的实例===" + oa2.getOrderProductNum());
        // 再次输出原型实例的值
        System.out.println("再次输出原型实例===" + personalOrder.getOrderProductNum());
    }
}