设计模式讲解与代码实践(二)——生成器

本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!
摘要:本文讲解了生成器(Builder)设计模式的使用目的、基本形态、各参与者以及调用时序,并结合示例代码,讲解了该设计模式在具体业务场景下的使用。

1 目的

生成器(Builder)将一个复杂的对象的构建过程与其表示(具体类)分离,重用创建过程。

2 基本形态

生成器的基本形态如类图2-1所示。
图2-1  生成器类图
图2-1 生成器类图

3 参与者

结合图2-1,下面介绍各类在生成器设计模式中扮演的角色。
3.1 Product
Product是生成器的工作产物(产品),它的各个组成部分由ConcreteBuilder生成,生成过程由Director控制。注意,这里的Product是泛指。因为各ConcreteBuilder所生成的产品差异可能很大,所以各Product可能是不同结构的,即各Product不一定实现了同一个接口或派生自同一个类。
3.2 Builder
Builder是抽象生成器,声明了生成器应包含的创建产品各个组成部件的方法。注意,这些方法只是生成对象的各组成部分,各方法相互独立,方法间无时序关系,也不存在“总”的生成方法。另外,Builder应包含获取生成的产品的方法。
3.3 ConcreteBuilder
ConcreteBuilder是具体的生成器,实现了Builder接口。每种不同的产品都有一个专属的ConcreteBuilder。
3.4 Director
Director是导向器,用于控制产品的生成过程。Director的控制对象是Builder,即通过Director实现了对各类型产品的生成过程的重用。
3.5 Client
Client是生成器模式的使用者。对Client来说,生成的最终产品Product的类型是明确的,生成Product所使用的具体生成器ConcreteBuilder也是明确的。

4 调用时序

生成器模式的典型调用时序如图4-1所示。
图4-1  生成器典型调用时序图
图4-1 生成器典型调用时序图
从图4-1可以看到,生成器的典型时序如下:

  1. 用户Client根据要构建的具体产品Product的类型用对应的具体生成器ConcreteBuilder对象实例化抽象生成器Builder;

  2. 用户Client使用抽象生成器Builder对象创建导向器Director对象;

  3. 用户Client调用导向器Director构建产品Product的方法Construct,Construct根据业务语义通过调用抽象生成器Builder生成产品组成部件的各方法BuildPartA、BuildPartB、BuildPartC完成产品Product的构建;

  4. 用户Client调用抽象生成器Builder获取产品的方法GetResult获取构建的产品Product;

5 代码实践

下面我们用一个业务场景实例来进一步讲解生成器的使用。
5.1 场景介绍
某购物平台需要将订单经过费用计算转换为账单。账单由基本价格、会员折扣、关税、运费等几个部分构成。对于“本地购”和“海外购”两种不同的商品,各价格构成部分的计算方法并不相同。现在使用生成器模式将账单计算过程与账单类型分离。
以下各节将介绍该场景各类的具体实现及其在生成器设计模式中所对应的参与者角色。

5.2 DomesticBill
DomesticBill类是国内账单类。对应于生成器模式的参与者,DomesticBill是抽象生成器IBillBuilder在本地购场景下的具体产品。DomesticBill的代码如下。

package demo.designpattern.builder;

/**
 * 国内账单
 * Created by LiMingzi on 2017-05-02.
 */
public class DomesticBill {
    /**
     * 原价
     */
    public float basicPrice = 0;
    /**
     * 总价
     */
    public float total = 0;
    /**
     * 折扣
     */
    public float discount = 0;
    /**
     * 运费
     */
    public float freight = 0;

    /**
     * 序列化
     *
     * @return 国内账单输出
     */
    @Override
    public String toString() {
        return "订单类型:本地购\n原价:" + basicPrice + "元\n折扣:" + discount + "元\n运费:" + freight + "元\n合计:" + total + "元\n";
    }
}

5.3 InternationalBill
InternationalBill类是国际账单类。对应于生成器模式的参与者,InternationalBill是抽象生成器IBillBuilder在海外购场景下的具体产品。InternationalBill的代码如下。

package demo.designpattern.builder;

/**
 * 国际账单
 * Created by LiMingzi on 2017-05-02.
 */
public class InternationalBill {
    /**
     * 原价
     */
    public float basicPrice = 0;
    /**
     * 总价
     */
    public float total = 0;
    /**
     * 折扣率
     */
    public float discountRate = 0;
    /**
     * 运费
     */
    public float freight = 0;
    /**
     * 税费
     */
    public float tax = 0;

    /**
     * 序列化
     *
     * @return 国际账单输出
     */
    @Override
    public String toString() {
        return "订单类型:海外购\n原价:" + basicPrice + "元\n折扣率:" + discountRate*100 + "%\n税费:" + tax + "元\n运费:" + freight + "元\n合计:" + total + "元\n";
    }
}

从上面的代码中我们可以看到,InternationalBill与DomesticBill其实是存在抽象基础的,即可以抽象出它们的接口或抽象类。本例中不抽象它们是为了让读者更好的理解生成器模式“各具体生成器生成的产品可以毫无关系”这一基本形态。
5.4 IBillBuilder
IBillBuilder是账单生成器接口。对应于生成器模式的参与者,IBillBuilder是抽象生成器。IBillBuilder的代码如下。

package demo.designpattern.builder;

/**
 * 账单生成器接口
 * Created by LiMingzi on 2017-05-02.
 */
public interface IBillBuilder {
    /**
     * 生成产品价格
     * @param basicPrice 基础价格
     */
    void buildPrice(float basicPrice);
    /**
     * 生成税费
     */
    void buildTax();

    /**
     * 生成运费
     */
    void buildFreight();

    /**
     * 生成折扣
     */
    void buildDiscount();

    /**
     * 获取账单
     * @return 账单
     */
    Object getBill();
}

从上面的代码中,我们可以看到,IBillBuilder声明的几个方法buildPrice、buildTax、buildFreight和buildDiscount分别对应于最终产品的一个部分,它们相互独立。我们并没有看到IBillBuilder中声明的“总”的产品生成方法。另外,32行,IBillBuilder还声明了获取产品的方法getBill。因为各具体产品之间没有关系,因此该方法声明的返回值类型为Object。
5.5 DomesticBillBuilder
DomesticBillBuilder是国内账单生成器类,实现了IBillBuilder接口,生成的产品是DomesticBill类对象。对应于生成器模式的参与者,DomesticBillBuilder是具体生成器。DomesticBillBuilder的代码如下。

package demo.designpattern.builder;

/**
 * 国内账单生成器
 * Created by LiMingzi on 2017-05-02.
 */
public class DomesticBillBuilder implements IBillBuilder {
    /**
     * 国内账单
     */
    private DomesticBill domesticBill = new DomesticBill();

    /**
     * 生成产品价格
     *
     * @param basicPrice 基础价格
     */
    @Override
    public void buildPrice(float basicPrice) {
        domesticBill.basicPrice = basicPrice;
        domesticBill.total+=basicPrice;
    }

    /**
     * 生成税费
     */
    @Override
    public void buildTax() {
        return;
    }

    /**
     * 生成运费
     */
    @Override
    public void buildFreight() {
        // 50以上包邮
        if(domesticBill.basicPrice>50){
            domesticBill.freight=0;
        }
        else{
            domesticBill.freight=10;
        }
        domesticBill.total+=domesticBill.freight;
    }

    /**
     * 生成折扣
     */
    @Override
    public void buildDiscount() {
        // 满100减20
        if(domesticBill.basicPrice>100){
            domesticBill.discount=20;
        }
        else{
            domesticBill.discount=0;
        }
        domesticBill.total-=domesticBill.discount;
    }

    /**
     * 获取账单
     *
     * @return 账单
     */
    @Override
    public DomesticBill getBill() {
        return domesticBill;
    }
}

上面的代码中,11行声明了产品domesticBill 作为成员变量以便保存最终产品的各个组成部分。28行生成税费方法buildTax因在国内账单中不涉及税费,直接返回。36行生成运费方法buildFreight的逻辑是商品原价满50元包邮,否则邮资10元。51行生成折扣方法buildDiscount的逻辑是满100元减20元。
另外,这个生成器的结构使其具有“一次性”的特点。即一个生成器对象只能生成一个产品。如果想让生成器对象可以复用,需要添加清除生成产品domesticBill 的方法。

5.6 InternationalBillBuilder
InternationalBillBuilder是国际账单生成器类,实现了IBillBuilder接口,生成的产品是InternationalBill类对象。对应于生成器模式的参与者,InternationalBillBuilder是具体生成器。InternationalBillBuilder的代码如下。

package demo.designpattern.builder;

import java.util.Calendar;
import java.util.Date;

/**
 * 国际账单生成器
 * Created by LiMingzi on 2017-05-02.
 */
public class InternationalBillBuilder implements IBillBuilder {
    /**
     * 国际账单
     */
    private InternationalBill internationalBill = new InternationalBill();
    /**
     * 生成产品价格
     *
     * @param basicPrice 基础价格
     */
    @Override
    public void buildPrice(float basicPrice) {
        internationalBill.basicPrice = basicPrice;
        internationalBill.total+=basicPrice;
    }

    /**
     * 生成税费
     */
    @Override
    public void buildTax() {
        // 大于起征点
        if(internationalBill.total*0.2f>50.0f){
            internationalBill.tax=internationalBill.total*0.2f-50.0f;
        }
        internationalBill.total+=internationalBill.tax;
    }

    /**
     * 生成运费
     */
    @Override
    public void buildFreight() {
        internationalBill.freight = 150;
        internationalBill.total+=internationalBill.freight;
    }

    /**
     * 生成折扣
     */
    @Override
    public void buildDiscount() {
        // 今日日期
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        // 周五全场八折,平时九折
        if(Calendar.FRIDAY== today.get(Calendar.DAY_OF_WEEK)){
            internationalBill.discountRate = 0.2f;
        }else{
            internationalBill.discountRate = 0.1f;
        }
        internationalBill.total=(1-internationalBill.discountRate)*internationalBill.total;
    }

    /**
     * 获取账单
     *
     * @return 账单
     */
    @Override
    public InternationalBill getBill() {
        return internationalBill;
    }
}

上面的代码中,14行声明了产品internationalBill作为成员变量以便保存最终产品的各个组成部分。30行生成税费方法buildTax的逻辑是税费为商品价格的20%,但有50元的免征额。42行生成运费方法buildFreight的逻辑是邮资统一150元。51行生成折扣方法buildDiscount的逻辑是周五全场八折,平时九折。

5.7 Order
Order类是订单类,是生成账单的依据。虽然Order类不是生成器模式的参与者,但一般情况下导向器Director构建产品都需要一些“素材”或“依据”,只不过提供的方式不同而已。Order的代码如下。

package demo.designpattern.builder;

/**
 * 订单
 * Created by LiMingzi on 2017-05-02.
 */
public class Order {
    /**
     * 产品名称
     */
    private String productName;
    /**
     * 产品单价
     */
    private int unitPrice;
    /**
     * 订单类型,0为国内订单,1为国际订单
     */
    private int orderType;
    /**
     * 用户是否为VIP
     */
    private boolean isVIP;

    /**
     * 获取产品名
     * @return 产品名
     */
    public String getProductName() {
        return productName;
    }

    /**
     * 获取产品单价
     * @return 产品单价
     */
    public int getUnitPrice() {
        return unitPrice;
    }

    /**
     * 获取订单类型
     * @return 订单类型,0为国内订单,1为国际订单
     */
    public int getOrderType() {
        return orderType;
    }

    /**
     * 获取是否为vip
     * @return 是否为vip
     */
    public boolean isVIP() {
        return isVIP;
    }

    /**
     * 构造方法
     * @param productName 产品名
     * @param unitPrice 单价
     * @param orderType 订单类型,0为国内订单,1为国际订单
     * @param isVIP 是否为vip
     */
    public Order(String productName, int unitPrice, int orderType, boolean isVIP) {
        this.productName = productName;
        this.unitPrice = unitPrice;
        this.orderType = orderType;
        this.isVIP = isVIP;
    }
}

5.8 BillDirector
BillDirector是账单导向器类,用于控制产品(账单)的构建过程。对应于生成器模式的参与者,BillDirector是导向器。BillDirector的代码如下。

package demo.designpattern.builder;

import java.util.Calendar;
import java.util.Date;

/**
 * 账单导向器
 * Created by LiMingzi on 2017-05-02.
 */
public class BillDirector {
    /**
     * 账单生成器
     */
    private IBillBuilder billBuilder;

    /**
     * 构造方法
     * @param billBuilder 账单生成器
     */
    public BillDirector(IBillBuilder billBuilder){
        this.billBuilder=billBuilder;
    }

    /**
     * 构建账单
     * @param order 订单
     */
    public void constructBill(Order order){
        billBuilder.buildPrice(order.getUnitPrice());
        // vip有折扣
        if(order.isVIP()){
            billBuilder.buildDiscount();
        }
        billBuilder.buildTax();
        // 今日日期
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        // 会员日全场免邮
        if(28 != today.get(Calendar.DAY_OF_MONTH)){
            billBuilder.buildFreight();
        }
    }
}

上面的代码中,14行声明了账单生成器接口对象billBuilder作为成员变量,并在构造方法里将其实例化。28行构建账单方法constructBill通过调用billBuilder构建产品的各个部分的方法逐步完成产品的构建。通过业务含义不难看出,账单的构建过程需要经过buildPrice、buildDiscount、buildTax和buildFreight等几个步骤,且这些步骤是不能打乱顺序的。另外,从代码中我们也可以看到,构建产品的各步骤根据构建素材并不一定都会执行。比如,代码31行,只有订单所属用户是VIP时才会生成折扣,否则没有折扣。
5.9 BillMgmt
BillMgmt是账单管理类,用于账单的各种管理操作。对应于生成器模式的参与者,BillMgmt是客户。BillMgmt的代码如下。

package demo.designpattern.builder;

/**
 * 账单管理类
 * Created by LiMingzi on 2017-05-02.
 */
public class BillMgmt {
    /**
     * 输出账单信息
     * @param order 订单
     */
    public void outputBill(Order order){
        // 账单生成器
        IBillBuilder billBuilder;
        // 国内订单
        if(0==order.getOrderType()){
            billBuilder = new DomesticBillBuilder();
        }
        else{
            billBuilder = new InternationalBillBuilder();
        }
        // 账单导向器
        BillDirector billDirector = new BillDirector(billBuilder);
        billDirector.constructBill(order);
        // 订单
        Object bill = billBuilder.getBill();
        System.out.println("商品名:"+ order.getProductName());
        // 国内订单
        if(0==order.getOrderType()){
            System.out.print(((DomesticBill) bill).toString());
        }
        else{
            System.out.print(((InternationalBill) bill).toString());
        }
    }
}

上面的代码中,输出账单信息方法outputBill根据订单信息生成账单,并输出账单。14行声明了抽象生成器变量billBuilder。15-21行,根据订单(即素材)是国内还是国际分别用DomesticBillBuilder和InternationalBillBuilder两个具体生成器类对象实例化billBuilder。23行用实例化后的billBuilder构建了导向器对象billDirector 。24行,调用导向器的方法constructBill构建账单。26行,调用生成器方法getBill获取账单对象。28-34行,根据订单是国内还是国际强制类型转化订单的类型(DomesticBill或InternationalBill),并调用产品的相应方法(toString)实现业务逻辑(输出账单信息)。
5.10 测试代码
为了测试本文中的代码,我们可以编写如下测试代码。

package demo.designpattern;

import demo.designpattern.builder.BillMgmt;
import demo.designpattern.builder.Order;

/**
 * Created by LiMingzi on 2017/4/26.
 */
public class Main {
    public static void main(String[] args) {
        builderTest();
    }

    public static void builderTest(){
        // 订单管理类对象
        BillMgmt billMgmt = new BillMgmt();
        // 国内订单
        Order domesticOrder = new Order("婴儿手口巾",20,0,false);
        billMgmt.outputBill(domesticOrder);
        System.out.println("-----------------------------------------------------------------");
        // 国际订单
        Order internationalOrder = new Order("婴儿车",3750,1,true);
        billMgmt.outputBill(internationalOrder);
    }
}

编译运行后,得到如下测试结果:

商品名:婴儿手口巾
订单类型:本地购
原价:20.0元
折扣:0.0元
运费:10.0元
合计:30.0元


商品名:婴儿车
订单类型:海外购
原价:3750.0元
折扣率:10.0%
税费:625.0元
运费:150.0元
合计:4150.0元

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值