设计模式讲解 — 生成器模式(3)

模式讲解

认识生成器模式

(1)生成器模式的功能

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

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

再直白点说,生成器模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用,具体的构造实现可以很方便的扩展和切换,从而可以灵活的组合来构造出不同的产品对象。

(2)生成器模式的构成

要特别注意,生成器模式分成两个很重要的部分:

  • 一个部分是Builder接口这边,这边是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;
  • 另外一个部分是Director这边,Director是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤的来执行。

不管如何变化,Builder模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在生成器模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式,所以要严格区分这两个部分。

Director实现整体构建算法的时候,遇到需要创建和组合具体部件的时候,就会把这些功能通过委托,交给Builder去完成。

(3)生成器模式的使用

应用生成器模式的时候,可以让客户端创造Director,在Director里面封装整体构建算法,然后让Director去调用Builder,让Builder来封装具体部件的构建功能,这就跟前面的例子一样。

还有一种退化的情况,就是让客户端Director融合起来,让客户端直接去操作Builder,就好像是指导者自己想要给自己构建产品一样。

(4)生成器模式的调用顺序示意图

生成器模式的调用顺序如图所示:
这里写图片描述

生成器模式的实现

(1)生成器的实现

实际上在Builder接口的实现中,每个部件构建的方法里面,除了部件装配外,也可以实现如何具体的创建各个部件对象,也就是说每个方法都可以有两部分功能,一个是创建部件对象,一个是组装部件。

在构建部件的方法里面可以实现选择并创建具体的部件对象,然后再把这个部件对象组装到产品对象中去,这样一来,Builder就可以和工厂方法配合使用了。

再进一步,如果在实现Builder的时候,只有创建对象的功能,而没有组装的功能,那么这个时候的Builder实现跟抽象工厂的实现是类似的。

这种情况下,Builder接口就类似于抽象工厂的接口,Builder的具体实现就类似于具体的工厂,而且Builder接口里面定义的创建各个部件的方法也是有关联的,这些方法是构建一个复杂对象所需要的部件对象,仔细想想,是不是非常类似呢。

(2)指导者的实现

在生成器模式里面,指导者承担的是整体构建算法部分,是相对不变的部分。因此在实现指导者的时候,把变化的部分分离出去是很重要的。

其实指导者分离出去的变化部分,就到了生成器那边,指导者知道整体的构建算法,就是不知道如何具体的创建和装配部件对象。

因此真正的指导者实现,并不仅仅是如同前面示例那样,简单的按照一定顺序调用生成器的方法来生成对象,并没有这么简单。应该是有较为复杂的算法和运算过程,在运算过程中根据需要,才会调用生成器的方法来生成部件对象。

(3)指导者和生成器的交互

在生成器模式里面,指导者和生成器的交互,是通过生成器的那些buildPart方法来完成的。在前面的示例中,指导者和生成器是没有太多相互交互的,指导者仅仅只是简单的调用了一下生成器的方法,在实际开发中,这是远远不够的。

指导者通常会实现比较复杂的算法或者是运算过程,在实际中很可能会有这样的情况:

  • 在运行指导者的时候,会按照整体构建算法的步骤进行运算,可能先运行前几步运算,到了某一步骤,需要具体创建某个部件对象了,然后就调用Builder中创建相应部件的方法来创建具体的部件。同时,把前面运算得到的数据传递给Builder,因为在Builder内部实现创建和组装部件的时候,可能会需要这些数据
  • Builder创建完具体的部件对象后,会把创建好的部件对象返回给指导者,指导者继续后续的算法运算,可能会用到已经创建好的对象
  • 如此反复下去,直到整个构建算法运行完成,那么最终的产品对象也就创建好了

通过上面的描述,可以看出指导者和生成器是需要交互的,方式就是通过生成器方法的参数和返回值,来回的传递数据。事实上,指导者是通过委托的方式来把功能交给生成器去完成。

(4)返回装配好的产品的方法

在标准的生成器模式里面,在Builder实现里面会提供一个返回装配好的产品的方法,在Builder接口上是没有的。它考虑的是最终的对象一定要通过部件构建和装配,才算真正创建了,而具体干活的就是这个Builder实现,虽然指导者也参与了,但是指导者是不负责具体的部件创建和组装的,因此客户端是从Builder实现里面获取最终装配好的产品。

当然在Java里面,我们也可以把这个方法添加到Builder接口里面。

(5)关于被构建的产品的接口

在使用生成器模式的时候,大多数情况下是不知道最终构建出来的产品是什么样的,所以在标准的生成器模式里面,一般是不需要对产品定义抽象接口的,因为最终构造的产品千差万别,给这些产品定义公共接口几乎是没有意义的。

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

考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。这个对象里面有很多类似这样的约束,那么该如何来创建这个对象呢?

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

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

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

还是来看看示例会比较清楚,为了实例简单,先不去考虑约束的实现,只是考虑如何通过Builder模式来构建复杂对象。

使用Builder模式来构建复杂对象,先不考虑带约束

源码地址:https://gitee.com/liupeifeng3514/design_pattern

(1)先看一下保险合同的对象,示例代码如下:

/**
 * 保险合同的对象
 */
public class InsuranceContract {
    /**
     * 保险合同编号
     */
    private String contractId;
    /**
     * 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订, 也就是说,"被保险人员"和"被保险公司"这两个属性,不可能同时有值
     */
    private String personName;
    /**
     * 被保险公司的名称
     */
    private String companyName;
    /**
     * 保险开始生效的日期
     */
    private long beginDate;
    /**
     * 保险失效的日期,一定会大于保险开始生效的日期
     */
    private long endDate;
    /**
     * 示例:其它数据
     */
    private String otherData;

    /**
     * 构造方法,访问级别是同包能访问
     */
    InsuranceContract(ConcreteBuilder builder) {
        this.contractId = builder.getContractId();
        this.personName = builder.getPersonName();
        this.companyName = builder.getCompanyName();
        this.beginDate = builder.getBeginDate();
        this.endDate = builder.getEndDate();
        this.otherData = builder.getOtherData();
    }

    /**
     * 示意:保险合同的某些操作
     */
    public void someOperation() {
        System.out.println("Now in Insurance Contract someOperation==" + this.contractId);
    }
}

注意:上例中的构造方法是default的访问权限,也就是不希望外部的对象直接通过new来构建保险合同对象;另外构造方法传入的是构建器对象,里面包含有所有保险合同需要的数据

(2)看一下具体的构建器的实现,示例代码如下:

/**
 * 构造保险合同对象的构建器
 */
public 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;
    }
    /**
     * 选填数据,被保险人员的名称
     * @param personName  被保险人员的名称
     * @return 构建器对象
     */
    public ConcreteBuilder setPersonName(String personName){
        this.personName = personName;
        return this;
    }
    /**
     *  选填数据,被保险公司的名称
     * @param companyName 被保险公司的名称
     * @return 构建器对象
     */
    public ConcreteBuilder setCompanyName(String companyName){
        this.companyName = companyName;
        return this;
    }
    /**
     * 选填数据,其它数据
     * @param otherData 其它数据
     * @return 构建器对象
     */
    public ConcreteBuilder setOtherData(String otherData){
        this.otherData = otherData;
        return this;
    }
    /**
     * 构建真正的对象并返回
     * @return 构建的保险合同的对象
     */
    public InsuranceContract build(){
        return new InsuranceContract(this);
    }

    // get方法
}

注意:上例中,构建器提供了类似于setter的方法,来供外部设置需要的参数,为何说是类似于setter方法呢?请注意观察,每个这种方法都有返回值,返回的是构建器对象,这样客户端就可以通过连缀的方式来使用Builder,以创建他们需要的对象。

(3)接下来看看此时的Client,如何使用上面的构建器来创建保险合同对象,示例代码如下:

public class Client {
    public static void main(String[] args) {
        // 创建构建器
        ConcreteBuilder builder = new ConcreteBuilder("001", 12345L, 67890L);
        // 设置需要的数据,然后构建保险合同对象
        InsuranceContract contract = builder.setPersonName("张三").setOtherData("test").build();
        // 操作保险合同对象的方法
        contract.someOperation();
    }
}

运行结果如下:

Now in Insurance Contract someOperation==001

看起来通过Builder模式构建对象也很简单,接下来,把约束加上去,看看如何实现。

使用Builder模式来构建复杂对象,考虑带约束规则

要带着约束规则构建复杂对象,大致的实现步骤与刚才的实现并没有什么不同,只是需要在刚才的实现上把约束规则添加上去。

通常有两个地方可以添加约束规则:

  • 一个是构建器的每一个类似于setter的方法,可以在这里进行单个数据的约束规则校验,如果不正确,就抛出IllegalStateException。
  • 另一个是构建器的build方法,在创建保险合同对象之前,对所有的数据都可以进行数据的约束规则校验,尤其是那些涉及到几个数据之间的约束关系,在这里校验会比较合适。如果不正确,同样抛出IllegalStateException。

这里选择在构建器的build方法里面,进行数据的整体校验,由于其它的代码都没有变化,因此就不去赘述了,新的build方法的示例代码如下:

    /**
     * 构建真正的对象并返回
     * @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);
    }
进一步,把构建器对象和被构建对象合并

其实,在实际开发中,如果构建器对象和被构建的对象是这样分开的话,可能会导致同包内的对象不使用构建器来构建对象,而是直接去使用new来构建对象,这会导致错误;另外,这个构建器的功能就是为了创建被构建的对象,完全可以不用单独一个类。

对于这种情况,重构的手法通常是将类内联化(Inline Class),放到这里来,简单点说就是把构建器对象合并到被构建对象里面去。

还是看看示例会比较清楚,示例代码如下:

/**
 * 保险合同的对象
 */
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 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;
        }
        /**
         * 选填数据,被保险人员的名称
         * @param personName  被保险人员的名称
         * @return 构建器对象
         */
        public ConcreteBuilder setPersonName(String personName){
            this.personName = personName;
            return this;
        }
        /**
         *  选填数据,被保险公司的名称
         * @param companyName 被保险公司的名称
         * @return 构建器对象
         */
        public ConcreteBuilder setCompanyName(String companyName){
            this.companyName = companyName;
            return this;
        }
        /**
         * 选填数据,其它数据
         * @param otherData 其它数据
         * @return 构建器对象
         */
        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 void someOperation(){
        System.out.println("Now in Insurance Contract someOperation=="+this.contractId);
    }
}

通过上面的示例可以看出,这种实现方式会更简单和直观。

此时客户端的写法也发生了一点变化,主要就是创建构造器的地方需要变化,示例代码如下:

public class Client {
    public static void main(String[] args) {
        // 创建构建器
        InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("001", 12345L, 67890L);
        // 设置需要的数据,然后构建保险合同对象
        InsuranceContract contract = builder.setPersonName("张三").setOtherData("test").build();
        // 操作保险合同对象的方法
        contract.someOperation();
    }
}

去测试一下看看,体会一下使用Builder模式来构建复杂对象,尤其是在构造方法需要大量参数,或者是构建带约束规则的复杂对象时的使用方式。

生成器模式的优缺点

松散耦合

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

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

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

更好的复用性

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

(1)生成器模式的本质

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

构建一个复杂的对象,本来就有构建的过程,以及构建过程中具体的实现,生成器模式就是用来分离这两个部分,从而使得程序结构更松散、扩展更容易、复用性更好,同时也会使得代码更清晰,意图更明确。

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

(2)何时选用生成器模式

建议在如下情况中,选用生成器模式:

  • 如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时
  • 如果同一个构建过程有着不同的表示时
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值