首页 > 代码库 > 设计模式学习04—建造者模式

设计模式学习04—建造者模式

一、动机与定义

     创建对象时,我们知道可以使用工厂方式来创建,使调用者和具体实现解耦,但是有一种情况,当要创建的多个对象之间重复性较大,只有创建步骤、组装顺序或者内部构件不同时,工厂模式就需要进一步的演化了,如我们去KFC,有很多种套餐,比如套餐1(薯条+可乐+汉堡),套餐2(鸡肉卷+薯条+可乐),这个套餐就是我们要获取的复杂对象,那么程序如何创建出这种对象呢。
     
     我们看到套餐的内容很多是一样的,那么我们是不是可以考虑将创建单个食品(如鸡肉卷、可乐等)方法提取出来,使用单独一个类协调这些食品的组合比较好呢,如下,具体食品创建者:
//食物的抽象创建者
public abstract class FoodBuilder {
    // 套餐
    protected SetMealProduct sm = new SetMealProduct();

    // 创建汉堡
    public abstract void buildHamburg();

    // 创建薯条
    public abstract void buildChips();

    // 创建可乐
    public abstract void buildCola();

    // 创建鸡肉卷
    public abstract void buildChickenRolls();

    public SetMealProduct getResult() {
        return sm ;
    }
}
     具体食品创建者:
//具体食品创建者:KFC
//这样未来还可以支持麦当劳
public class KFCFoodBuilder extends FoodBuilder {

    @Override
    public void buildHamburg() {
        sm.getFoodList().add( "汉堡");
    }

    @Override
    public void buildChips() {
        sm.getFoodList().add( "薯条");
    }

    @Override
    public void buildCola() {
        sm.getFoodList().add( "可乐");
    }

    @Override
    public void buildChickenRolls() {
        sm.getFoodList().add( "鸡肉卷" );
    }

}
     套餐产品类:
//套餐
public class SetMealProduct {
    // 套餐名称
    private String name;
    // 套餐中的食品集合
    private List<String> foodList = new ArrayList<String>();

    public List<String> getFoodList() {
        return foodList ;
    }

    public void setFoodList(List<String> foodList) {
        this.foodList = foodList;
    }

    public String getName() {
        return name ;
    }

    public void setName(String name) {
        this.name = name;
    }

}
     负责生成套餐,组装顺序的类:
//套餐一(薯条+可乐+汉堡)
class FirstDirector {

    private FoodBuilder fb;

    public FirstDirector(FoodBuilder fb) {
        this.fb = fb;
    }

    public SetMealProduct construct() {
        fb.buildChips(); // 薯条
        fb.buildHamburg(); // 汉堡
        fb.buildCola(); // 可乐
        return fb .getResult();
    }
}

// 套餐二(鸡肉卷+薯条+可乐)
class SecondDirector {

    private FoodBuilder fb;

    public SecondDirector(FoodBuilder fb) {
        this.fb = fb;
    }

    public SetMealProduct construct() {
        fb.buildChickenRolls(); // 鸡肉卷
        fb.buildChips(); // 薯条
        fb.buildCola(); // 可乐
        return fb .getResult();
    }
}
     调用者:
        FirstDirector fDirector = new FirstDirector( new KFCFoodBuilder());
        SecondDirector sDirector = new SecondDirector( new KFCFoodBuilder());
        fDirector.construct();
        sDirector.construct();
     这样的话,调用者不需要关心底层如何创建,如何组装的,直接获得就行了,其实这就是创建者模式的思想。

二、结构与类图

     建造者模式主要用于分步骤的创建一些复杂的对象,将这些步骤一个个的独立起来,使用一个指挥者来控制这些步骤的顺序。
     

     建造者模式有4个角色:
          1、Builder:抽象建造者,定义了产品的各个部件的创建方法,具体如何构建由子类实现;
          2、ConcreteBuilder:具体建造者,实现了各个部件的创建,返回一个创建好的对象;
          3、Director:指挥者,也叫导演者,负责各个部件的组装、步骤的执行顺序,调用Builder创建具体产品,它是建造者模式的核心,有两个职责,1个是隔离了用户与产品的生成过程,另一个负责产品的生产过程;
          4、Product:具体产品,由各个部件构成的复杂产品。

三、适用场景及效果(优缺点)

     建造者模式核心是创建者和指挥者,创建者负责具体创建的部件、零件或步骤,指挥者负责步骤顺序,部件、零件组装顺序,多少等。所以以下场景可以考虑使用建造者模式:
     1、产品类非常复杂,产品类的组成部件或每个具体创建的步骤比较固定,需要根据不同的组装方法或执行顺序产生不同的产品结果时,可以考虑使用建造者模式,如开篇的KFC的例子,部件(可乐、鸡肉卷等)稳定,组装方法不稳定,产生不通结果(套餐);有的时候,这种情况也可以考虑使用模版方法,模版方法更简单一些,但没有建造者这么灵活;
     2、产品类非常复杂,产品类部件组装方法或创建步骤比较固定,需要根据不同的部件或步骤,产生不同的产品时,即需要用相同的创建过程创建不同的产品时,举个常见的例子,生活中我们自己组装电脑,组装电脑的方法基本固定,将cpu,硬盘,内存,显卡等等插到主板上,连接显示器鼠标键盘,开机,这个步骤就是指挥者干的事,但是具体的cpu是intel还是AMD的,内存是金士顿还是三星的等等是不固定,提供cpu,内存,硬盘等部件就是创建者干的事;
     3、产品类非常复杂,具体部件和步骤,还有组装方法,顺序都经常变化,也可以使用这个模式,不过此时最好和其他模式一起来用,比如和模版模式一起使用;
     4、生成的产品对象的属性相互依赖,需要指定执行顺序时,可以考虑;
     
     使用后的效果(优点):
     1、封装、解耦,这个是创建型设计模式基本的作用,封装底层,调用者不需要知道如何复杂的创建,只需要直接使用即可。
     2、扩展方便,可以很方便的增加具体的建造者,用户使用不同建造者就能得到不同的产品。
     3、产品组装过程、执行顺序和具体部件、步骤分离,非常灵活,很容易扩展出新的组装方法和执行顺序。
     4、建造者模式可以对复杂产品的创建过程进行精细化控制,比起其他创建型模式能更好的反应产品的创建过程,每个建造者的具体步骤都是独立的,因此可以逐步细化,而不对其他模块产生影响。
     
     缺点:如果产品内部变化复杂,可能会导致需要定义很多具体建造者(Builder)和指挥者(Director),导致系统变得非常庞大。
     建造者模式一般创建的产品都具有较多的共同点,组成部分也相似,如果产品之间差异较大,则不适合使用建造者模式。

五、模式扩展

     1、简化模式,如果变化在于指挥者,组装步骤和执行顺序,建造者只有1个,那么可以省略抽象建造者。再简单的,如果组装方法和步骤稳定,可以省略指挥者,直接将步骤合并到建造者中。
     2、你会发现,建造者模式和工厂模式非常相似,其实他俩还是有区别的,建造者主要功能是对部件的组装,步骤的执行顺序进行控制,也就是部件已经定了,我来控制组装方法,而工厂模式的侧重点是在于创建,如何创建具体的部件,至于怎么组装不是它关心的。可以参考上面提到的组装电脑的例子,建造者模式重点在于如何组装,而工厂模式在于如何创建cpu,创建内存等。
     工厂模式更像一种思想,将不确定的,易变的东西延迟到子类去实现(或者说留给以后实现),达到某种程度的解耦。抽象工厂也是接口,但它不是因为不确定,而是产品等级,产品族太多,进行了一层封装而已。
     建造者模式不是建造模式,有人说改名“导演模式”更能反应它的本质,两者都是一种封装,建造者是过程性的封装,工厂是结构性的封装。
     3、建造者模式经常和其他模式混合使用,比如和模版模式混合使用,建造者模式中有一个角色被忽略了,就是部件,部件的创建很多时候使用模版模式就非常合适。还有这些部件的创建使用了工厂模式、原型模式等,甚至有时候结合单例来控制类实例的生成,不论用什么模式,不论怎么组合,最终的目的一定是能给系统带来好处,而不是臃肿的结构,复杂的代码。
     4、灵活配置创建顺序,我们可以把建造者模式中的指挥者角色配置到配置文件中,定义好统一标准,读取配置文件来执行创建者的各个方法,这样后续修改指挥者的执行顺序就非常方便了。其实很多模式都可以把步骤提取到配置文件中,虽然灵活,但是代码复杂了,配置文件管理不善也是个问题,这也是一个取舍。
     5、建造者模式真的只能创建对象吗?前面我们一直说建造者模式控制部件组装,步骤的执行顺序都是为了创建对象,其实还有更深一个层次,控制步骤的执行顺序不仅仅是创建对象,还有初始化对象工作,甚至对象的执行步骤工作,比如我们使用建造者模式创建一个文档对象,完全可以获取一个已经打开的文档,未打开的文档,已经初始化一些内容的文档等等。

     小结,建造者模式其实就是将复杂对象的创建分解成很多精细的小步骤或小部件,然后使用一个指挥者的类来控制这些步骤的执行或部件的组装,从而达到解耦合,易扩展的目的,调用者只需要使用指挥类和建造类,从而达到了对底层对象创建的封装目的。因为建造者是抽象类,不是具体实现,后续扩展也不影响现有代码,符合了开闭原则。