设计模式(3)-建造者模式(Builder Pattern)

建造者模式是对象的创建模式,可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表下那个的产品对象。
也就是说使用建造者模式,可以使客户端不需要知道所生成的产品对象有哪些零件,每个产品对应两件彼此有何不同,是怎么建造出来的,以及怎样组成产品。

建造者模式涉及四个角色

1、抽象建造者(Builder):给出一个抽象接口,以规范产品对象的各个组成成分的建造,接口中申明产品的建造方法,和结果的返还方法。
2、具体建造者(ConcreteBuilder):实现抽象建造者Builder所声明的接口。并在建造完成后提供产品的实例。
3、导演者(Director):调用具体建造者去创建产品对象。
4、产品类(Product):产品便是建造中的复杂对象,一般来说,一个系统中会有多于一个的产品类,而且这些产品类不一定有共同的接口,而完全可以使不相关联的。

导演角色将创建产品的请求委派给具体建造者,具体建造者根据建造方法建造完成,并提供产品的实例,然后产品类去接收产品的实例。
看看下面的例子:

Builder.java(抽象建造者)
abstract public class Builder {
    /**
     * 产品零件构造方法
     */
    public abstract void buildPart1();

    /**
     * 产品零件构造方法
     */
    public abstract void buildPart2();

    /**
     * 产品返还方法
     * @return
     */
    public abstract Product retrieveResult();


}
Product.java(产品类)
public class Product {
    private List<String> list = new ArrayList<>();

    public void addStr(String str){
        list.add(str);
    }

    public void show(){
        for(String s:list){
            System.out.println("合体:"+s);
        }
        System.out.println("合体成功!");
    }

}
ConcreteBuilder.java(具体建造者)集成抽象建造者,并实现产品的建造方法。最后返还产品的实例。
public class ConcreteBuilder extends Builder {
    private Product product = new Product();

    @Override
    public void buildPart1() {
        product.addStr("我来组成头部!");

    }

    @Override
    public void buildPart2() {
        product.addStr("我来组成身体- -");
    }

    @Override
    public Product retrieveResult() {
        return product;
    }
}
Director.java(导演者)调用具体建造者创建产品。
public class Director {
    /**
     * 创建产品
     * @param builder
     */
    public void Construct( Builder builder){
        builder.buildPart1();
        builder.buildPart2();
        builder.retrieveResult();
    }

}
Client.java(客户端调用)
public class Client {
    public static void main(String[] args) {
        //先实例导演和具体建造者
        Director director = new Director();
        Builder builder = new ConcreteBuilder();
        //导演者调用具体建造者去建造产品
        director.Construct(builder);
        //产品类去接收组装好的产品
        Product product = builder.retrieveResult();
        //产品展示
        product.show();


}
输出结果
合体:我来组成头部!
合体:我来组成身体- -
合体成功!
以上是经典的Builder模式。在《Effective Java》这本书中,第二条是:当构造方法参数过多时使用builder模式,这里的Builder模式又是怎么使用的呢,下面我们来看看。

举个例子,一个食品包装上营养成分的类(NutritionFacts),里面有几个必需的属性——每次建议的摄入量,每罐的份量和每份卡路里 ,以及超过20个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。

public class NutritionFacts {
    private final int servingSize;  // 必须的属性
    private final int servings;     // 必须的属性
    private final int calories;     // 可选的属性
    private final int fat;          //可选的属性
    private final int sodium;       // 可选的属性
    private final int carbohydrate; // 可选的属性
}

如何创建这个对象呢?一种可选的方法是使用构造方法,第一个构造方法只包含两个必需的参数,第二个构造方法中,增加一个可选参数,第三个构造方法中再增加一个可选参数,依次类推,直到构造方法中包含了所有的参数。

public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

这样做,参数较少的时候问题还不大,一旦参数多了,代码可读性就很差,并且难以维护,如果一旦写反了两个参数的值,编译不会报错,但很明显我们会出bug。

第二种方式:当在构造方法中遇到许多可选参数时,另一种选择是JavaBeans模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用setter方法来设置每个必需的参数和可选参数。

public class NutritionFacts {

    private int servingSize  = -1; // 必须的属性; 
    private int servings     = -1; // 必须的属性; 
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }

    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)    { servings = val; }
    public void setCalories(int val)    { calories = val; }
    public void setFat(int val)         { fat = val; }
    public void setSodium(int val)      { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

这种方法看起来可读性不错,而且易于维护。调用者,只需要创建一个空的对象,然后传入我们需要的参数就可以了。
1、对象会产生不一致的状态。当你想要传入5个参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实对象并没有创建完成。
2、类是可变的了,不可变类所有好处都不复存在。

卖了这么多关子,就是为了来说说我们的第三种方法Builder模式。

package com.lw.study.designModel.BuilderPattern;

/**
 * @author: 
 * @Date: 14:15 2018/7/24
 * 营养成分
 */
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder{
        private final int servingSize;
        private final int servings;
        private  int calories = 0;
        private  int fat = 0;
        private  int sodium = 0;
        private  int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

}

它是Builder模式的一种形式,客户端不直接调用所需的对象,而是调用构造方法(或静态工厂),并使用所有必需的参数,并获得一个builder对象。然后,客户端调用builder对象的setter相似方法来设置每个可选参数。最后,客户端调用一个无参的build方法来生成对象,该对象通常是不可变的。就像下面这样:

  NutritionFacts coca = new NutritionFacts.Builder(120,20).calories(20)
                                                .carbohydrate(40)
                                                 .build();

NutritionFacts类是不可变的,所有的参数默认值都在一个地方。builder的setter方法返回builder本身,这样调用就可以被链接起来,从而生成一个流畅的API。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值