设计模式 建造者模式

来源:互联网 发布:js点赞心形动态效果 编辑:程序博客网 时间:2024/06/02 06:22

1 场景问题

1.1 继续导出数据的应用框架

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

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

在工厂方法模式章节里面,讨论并使用工厂方法模式来解决了如何选择具体导出方式的问题,并没有涉及到每种方式具体如何实现。换句话说,在讨论工厂方法模式的时候,并没有讨论如何实现导出成文本、Xml等具体的格式,本章就来讨论这个问题。

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

导出的文件,不管什么格式,都分成三个部分,分别是文件头、文件体和文件尾

在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔

在文件体部分,需要描述如下信息:表名称、然后分条描述数据。对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔。

在文件尾部分,需要描述如下信息:输出人

现在就要来实现上述功能。为了演示简单点,在工厂方法模式里面已经实现的功能,这里就不去重复了,这里只关心如何实现导出文件,而且只实现导出成文本格式和XML格式就可以了,其它的就不去考虑了。

1.2 不用模式的解决方案

不就是要实现导出数据到文本文件和XML文件吗,其实不管什么格式,需要导出的数据是一样的,只是具体导出到文件中的内容,会随着格式的不同而不同。

1.先来把描述文件各个部分的数据对象定义出来,先看描述输出到文件头的内容的对象,示例代码如下:

public class ExportHeader {    /**     * 分公司或门市点编号     */    private String depId;    /**     * 导出数据的日期     */    private String exportDate;    public String getDepId() {        return depId;    }    public void setDepId(String depId) {        this.depId = depId;    }    public String getExportDate() {        return exportDate;    }    public void setExportDate(String exportDate) {        this.exportDate = exportDate;    }}

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

public class ExportContent {    /**     * 产品编号     */    private String productId;    /**     * 数量     */    private int amount;    /**     * 价格     */    private double price;    public String getProductId() {        return productId;    }    public void setProductId(String productId) {        this.productId = productId;    }    public int getAmount() {        return amount;    }    public void setAmount(int amount) {        this.amount = amount;    }    public double getPrice() {        return price;    }    public void setPrice(double price) {        this.price = price;    }}

文件尾的内容的对象,示例代码如下:

public class ExportFoot {    /**     * 输出人     */    private String user;    public String getUser() {        return user;    }    public void setUser(String user) {        this.user = user;    }}

2.接下来具体的看看导出的实现,先看导出数据到文本文件的对象,主要就是要实现拼接输出的内容,示例代码如下:

public class ExportTxt {    public void export(ExportHeader eh, ExportFoot ef, Map<String, Collection<ExportContent>> dataMap) {        StringBuffer sb = new StringBuffer();        sb.append("公司编号:" + eh.getDepId());        sb.append("\n");        for (String tableName: dataMap.keySet()) {            sb.append("表名:" + tableName + "\n");            Iterator<ExportContent> iter = dataMap.get(tableName).iterator();            while (iter.hasNext()) {                ExportContent ec = iter.next();                sb.append(ec.getProductId() + "\t");                sb.append(ec.getAmount() + "\t");                sb.append(ec.getPrice()+ "\t");                sb.append("\n");            }        }        sb.append("输出人:" + ef.getUser() + "\n");        System.out.println(sb.toString());    }}

3.接下来看看导出数据到XML文件的对象,按照xml格式随意拼接的,仅演示和txt格式的区别, 这里不考虑其语法的正确性:

public class ExportXml {    public void export(ExportHeader eh, ExportFoot ef, Map<String, Collection<ExportContent>> dataMap) {        StringBuffer sb = new StringBuffer();        sb.append("公司编号:<header>" + eh.getDepId()+"</header>");        sb.append("\n");        for (String tableName: dataMap.keySet()) {            sb.append("表名:" + tableName + "\n");            Iterator<ExportContent> iter = dataMap.get(tableName).iterator();            while (iter.hasNext()) {                ExportContent ec = iter.next();                sb.append("<content>");                sb.append(ec.getProductId() + "\t");                sb.append(ec.getAmount() + "\t");                sb.append(ec.getPrice()+ "\t");                sb.append("</content>");                sb.append("\n");            }        }        sb.append("输出人:<footer>" + ef.getUser() + "</footer>\n");        System.out.println(sb.toString());    }}

4.看看客户端,如何来使用这些对象,示例代码如下:

public class Client {    public static void main(String[] args) {        // 准备测试数据        ExportHeader eh = new ExportHeader();        eh.setDepId("一分公司");        eh.setExportDate("2010-05-18");        ExportFoot ef = new ExportFoot();        ef.setUser("mengqa");        ExportContent edm1 = new ExportContent();        edm1.setProductId("产品001号");        edm1.setPrice(100);        edm1.setAmount(80);        ExportContent edm2 = new ExportContent();        edm2.setProductId("产品002号");        edm2.setPrice(99);        edm2.setAmount(55);        Collection<ExportContent> col = new ArrayList<ExportContent>();        col.add(edm1);        col.add(edm2);        Map<String, Collection<ExportContent>> dataMap = new HashMap<String, Collection<ExportContent>>();        dataMap.put("销售记录表", col);        ExportTxt ext = new ExportTxt();        ext.export(eh, ef, dataMap);        ExportXml epx = new ExportXml();        epx.export(eh, ef, dataMap);    }}

1.3 有何问题

仔细观察上面的实现,会发现,不管是输出成文本文件,还是输出到XML文件,在实现的时候,步骤基本上都是一样的,都大致分成了如下四步:

1.先拼接文件头的内容;2.然后拼接文件体的内容;3.再拼接文件尾的内容;4.最后把拼接好的内容输出出去成为文件;

也就是说,对于不同的输出格式,处理步骤是一样的,但是具体每步的实现是不一样的。按照现在的实现方式,就存在如下的问题:

1.构建每种输出格式的文件内容的时候,都会重复这几个处理步骤,应该提炼出来,形成公共的处理过程;

2.今后可能会有很多不同输出格式的要求,这就需要在处理过程不变的情况下,能方便的切换不同的输出格式的处理;

换句话来说,也就是构建每种格式的数据文件的处理过程,应该和具体的步骤实现分开,这样就能够复用处理过程,而且能很容易的切换不同的输出格式。

可是该如何实现呢?

2 解决方案

2.1 生成器模式来解决

应用生成器模式来解决的思路

仔细分析上面的实现,构建每种格式的数据文件的处理过程,这不就是构建过程吗?而每种格式具体的步骤实现,不就相当于是不同的表示吗?因为不同的步骤实现,决定了最终的表现也就不同。也就是说,上面的问题恰好就是生成器模式要解决的问题。

要实现同样的构建过程可以创建不同的表现,那么一个自然的思路就是先把构建过程独立出来,在生成器模式中把它称为指导者,由它来指导装配过程,但是不负责每步具体的实现。当然,光有指导者是不够的,必须要有能具体实现每步的对象,在生成器模式中称这些实现对象为生成器。

这样一来,指导者就是可以重用的构建过程,而生成器是可以被切换的具体实现。前面的实现中,每种具体的导出文件格式的实现就相当于生成器。

2.2 模式结构和说明

生成器模式

Builder:生成器接口,定义创建一个Product对象所需的各个部件的操作。

ConcreteBuilder:具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。

Director:指导者,也被称为导向者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象。

Product:产品,表示被生成器构建的复杂对象,包含多个部件。

2.3 使用生成器模式重写示例

要使用生成器模式来重写示例,重要的任务就是要把指导者和生成器接口定义出来。指导者就是用来执行那四个步骤的对象,而生成器是用来实现每种格式下,对于每个步骤的具体实现的对象。

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

public interface ExportBuilder {    public void buildHeader(ExportHeader eh);    public void buildContent(Map<String, Collection<ExportContent>> dataMap);    public void buildFoot(ExportFoot ef);}

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

public class TxtBuilder implements ExportBuilder {    private StringBuffer sb = new StringBuffer();    @Override    public void buildHeader(ExportHeader eh) {        sb.append("公司编号:" + eh.getDepId());        sb.append("\n");    }    @Override    public void buildContent(Map<String, Collection<ExportContent>> dataMap) {        for (String tableName: dataMap.keySet()) {            sb.append("表名:" + tableName + "\n");            Iterator<ExportContent> iter = dataMap.get(tableName).iterator();            while (iter.hasNext()) {                ExportContent ec = iter.next();                sb.append(ec.getProductId() + "\t");                sb.append(ec.getAmount() + "\t");                sb.append(ec.getPrice()+ "\t");                sb.append("\n");            }        }    }    @Override    public void buildFoot(ExportFoot ef) {        sb.append("输出人:" + ef.getUser() + "\n");    }    public StringBuffer getResult() {        return sb;    }}

导出数据到XML文件的生成器实现类似, 不赘述了..

指导者:

public class Client {    public static void main(String[] args) {        // 准备测试数据        ExportHeader eh = new ExportHeader();        eh.setDepId("一分公司");        eh.setExportDate("2010-05-18");        ExportFoot ef = new ExportFoot();        ef.setUser("mengqa");        ExportContent edm1 = new ExportContent();        edm1.setProductId("产品001号");        edm1.setPrice(100);        edm1.setAmount(80);        ExportContent edm2 = new ExportContent();        edm2.setProductId("产品002号");        edm2.setPrice(99);        edm2.setAmount(55);        Collection<ExportContent> col = new ArrayList<ExportContent>();        col.add(edm1);        col.add(edm2);        Map<String, Collection<ExportContent>> dataMap = new HashMap<String, Collection<ExportContent>>();        dataMap.put("销售记录表", col);        TxtBuilder txtBuilder = new TxtBuilder();        Director director = new Director(txtBuilder);        director.exportBuild(eh, ef, dataMap);        System.out.println(txtBuilder.getResult().toString());        XmlBuilder xmlBuilder = new XmlBuilder();        Director director2 = new Director(xmlBuilder);        director2.exportBuild(eh, ef, dataMap);        System.out.println(xmlBuilder.getResult().toString());    }}

客户端:

public class Client {    public static void main(String[] args) {        // 准备测试数据        ExportHeader eh = new ExportHeader();        eh.setDepId("一分公司");        eh.setExportDate("2010-05-18");        ExportFoot ef = new ExportFoot();        ef.setUser("mengqa");        ExportContent edm1 = new ExportContent();        edm1.setProductId("产品001号");        edm1.setPrice(100);        edm1.setAmount(80);        ExportContent edm2 = new ExportContent();        edm2.setProductId("产品002号");        edm2.setPrice(99);        edm2.setAmount(55);        Collection<ExportContent> col = new ArrayList<ExportContent>();        col.add(edm1);        col.add(edm2);        Map<String, Collection<ExportContent>> dataMap = new HashMap<String, Collection<ExportContent>>();        dataMap.put("销售记录表", col);        TxtBuilder txtBuilder = new TxtBuilder();        Director director = new Director(txtBuilder);        director.exportBuild(eh, ef, dataMap);        System.out.println(txtBuilder.getResult().toString());        XmlBuilder xmlBuilder = new XmlBuilder();        Director director2 = new Director(xmlBuilder);        director2.exportBuild(eh, ef, dataMap);        System.out.println(xmlBuilder.getResult().toString());    }}

看了上面的示例会发现,其实生成器模式也挺简单的,好好理解一下。通过上面的讲述,应该能很清晰的看出生成器模式的实现方式和它的优势所在了,那就是对同一个构建过程,只要配置不同的生成器实现,就会生成出不同表现的对象。

何时选用生成器模式

如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时;

如果同一个构建过程有着不同的表示时;

原创粉丝点击