设计模式 - 建造者模式

2020-06-29 15:21:09 浏览数 (1)

「行情不行,就得多努力」

建建:这场疫情啊,让我明白了,有一个房子,能让你稳定住所;有一辆车,能让你出行无忧。

子乾:听这话意思是要买房买车了呢。

建建:要不资助点?

子乾:那明天你跟我一起出门挣点。

建建:去哪,在哪个位置?

子乾:以前啊,就在那大商场门口就行,一个碗,一根棍,一个铺盖趴一天,天天收入好几百。现在行情不行了呀。

建建:还是给我爸打个电话吧,明天去提车。

子乾:那建总要宝马还是大奔,是保时捷还是劳斯莱斯?

建建:行吧,那我就要一辆“环环相扣”的吧。

那么问题来了,面对众多的车牌,从在厂生产,到 4S 店销售,再到客户手中,这样一个复杂“车”对象,购买和生产的来龙去脉怎么表示在代码里面?

那这就需要 「建造者模式」了。

什么是 建造者模式?(别名生成器模式)

Builder Pattern: Separate the construction of a complex object from its representation so that the same construction process can create different representations.

看不懂看下面:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

它是对象创建型模式

先来看一下类图:

角色:

指挥者 Director

抽象建造者 Builder

具体建造者 SubBuilder1、SubBuilder2 ...

目标产品 Product

如果你想买一辆车,Product 是车,Builder 是抽象的车制造厂,SubBuilder1 、SubBuilder2 可以是宝马制造厂、红旗制造厂、特斯拉制造厂等等... Director 则可以是汽车销售员。

你只需要告诉销售员想要的车牌,背后经过一系列生产组装(不需你管),一辆完整的车就送到了你面前。

再比如,现在饭店都有套餐,你去了想吃一份套餐,Product 是一份套餐,Builder 是抽象的套餐制作机,SubBuilder1、SubBuilder2 可以是具体的某一种套餐的制作机,麻辣香锅焖面套餐带米饭雪碧、三荤三素带米饭可乐、五荤五素带米饭等等... Director 则可以是饭店服务员。

你只需要告诉服务员想要的套餐名字,大厨们一顿操作(无需你管),一份完整的套餐就送到了你面前。

我们以饭店实例做一下拆解:

类图:

目标产品 Meal制作类 SubMealBuilderA 或者 SubMealBuilderB 完成制作,而为了“开闭原则”,他们有共同的制作类父类 MealBuilder 类,这在客户端的调用过程中就用到了“里氏代换”原则。

服务员 Waiter 则通过制作类的父类,完成调用。实际 Waiter 中使用的具体制作过程函数,肯定是制作类子类的。因为具体实现都在子类呀。

客户和服务员打交道就可以了,只需要告诉服务员想要的具体产品。

下面通过代码来解析一下这个过程:

首先,要有一个目标产品类 Meal

它包含该产品的具体组成,这份套餐中包含食物(麻辣香锅焖面套餐带米饭、三荤三素带米饭...)、饮料(可乐、雪碧、脉动...)

代码语言:javascript复制
package com.sample.buildpattern;

// 目标产品类
public class Meal {

    private String food;
    private String drink;

    public String getFood() {
        return food;
    }

    public void setFood(String food) {
        this.food = food;
    }

    public String getDrink() {
        return drink;
    }

    public void setDrink(String drink) {
        this.drink = drink;
    }
}

然后,定义一个制作类父类 MealBuilder

该类依赖于目标产品类,因为该系列的类负责制作完成一份完整的目标产品。

因此这里面要包含食物、饮料的制作过程,因为目标产品 Meal 定义为了 protected,所以还需要一个类将生成的目标产品外用,即 getMeal()。

代码语言:javascript复制
package com.sample.buildpattern;

public abstract class MealBuilder {
    protected Meal meal = new Meal();

    public abstract void buildFood();
    public abstract void buildDrink();
    public abstract Meal getMeal();
}

要定义 MealBuilder 具体的子类,每个子类负责制作不同的套餐

SubMealBuilderA 负责制作麻辣香锅焖面套餐带米饭可乐套餐,SubMealBuilderB 负责制作三荤三素带米饭可乐套餐。

SubMealBuilderA:

代码语言:javascript复制
package com.sample.buildpattern;

public class SubMealBuilderA extends MealBuilder{

    @Override
    public void buildFood() {
        System.out.println("麻辣香锅焖面套餐加米饭");
    }

    @Override
    public void buildDrink() {
        System.out.println("雪碧");
    }
}

SubMealBuilderB:

代码语言:javascript复制


public class SubMealBuilderB extends MealBuilder {

    @Override
    public void buildFood() {
        System.out.println("三荤三素加米饭");
    }

    @Override
    public void buildDrink() {
        System.out.println("可乐");
    }
}

定义指挥者服务员类 Waiter。

它负责调用(告诉)制作人员,客户需要哪一款套餐。

因此该类需要聚合制作类父类,手里握着制作类们的父亲,儿子们不敢不听话,想使用哪个儿子制作产品就可以调用哪一个。

制作类的父类为 Waiter 类私有属性,通过 set 方法为其赋值,并且在 construct 方法中,获得制作类制作的产品。

代码语言:javascript复制
package com.sample.buildpattern;

public class Waiter {
    private MealBuilder mealBuilder;

    public void setMealBuilder(MealBuilder mealBuilder){
        this.mealBuilder = mealBuilder;
    }

    public Meal construct(){
        mealBuilder.buildFood();
        mealBuilder.buildDrink();

        return mealBuilder.getMeal();
    }
}

完成客户端类,客户端用户直接和服务员类 Waiter 打交道,告诉服务员需要什么套餐,服务员进行安排后,用户拿到目标套餐。

用户想要麻辣香锅焖面套餐加米饭可乐,因此指定该套餐,服务员收到后传达,制作人员一顿操作,用户拿到 meal,可以输出看是不是想要的套餐。

代码语言:javascript复制
package com.sample.buildpattern;

public class Client {

    public static void main(String[] args){
        // 指定套餐
        MealBuilder mealBuilder = new SubMealBuilderA();

        //服务员传达
        Waiter waiter = new Waiter();
        waiter.setMealBuilder(mealBuilder);

        //返回食品
        Meal meal = waiter.construct();
        meal.getDrink();
        meal.getFood();

    }
}

结果:完美~

以上就是该模式的一个细致拆分分享。

通过以上分析,隐隐约约可以感受到该模式的一些优缺点。类挺多,开闭原则好像也符合,对吧。

下面我们总结一下它的优缺点:

优点:

▏客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使相同的创建过程可以创建不同的产品对象;

▏建造者类符合开闭原则,每个独立,很方便的替换、新增、删除;

▏可以更加精细的控制产品的创建过程。

缺点:

▏建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,不适合使用建造者模式;

▏产品内部变化复杂,可能需要创建很多具体的建造者,系统变的庞大臃肿复杂。

建造者模式适用的环境:

☆ 需要生成的产品对象有复杂的内部结构,比如多个成员变量,而且还是引用类型变量;

☆ 需要生成的产品对象的属性相互依赖,需要指定生成顺序;

☆ 对象的创建过程独立于创建该对象的类。创建过程在指挥者类中,不在建造者类,也不在客户端类;

☆ 复杂对象的创建和使用满足隔离要求。

诶,有没有一个小疑惑。

建造者模式是:告诉它,想要什么,就可以给你返回什么。

而抽象工厂模式也是,想要什么,告诉它,就可以给你返回什么。

它们俩有哪些区别呢?

从字面理解,工厂就是生产产品,生产某一样产品。建造者模式是构建,构建一件产品。

▌建造者模式返回的是一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成一个产品族。回顾这个:设计模式 - 抽象工厂模式;

▌抽象工厂模式中,客户端实例化工厂类,调用工厂方法,获得目标产品。而建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者来引导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整复杂对象。

▌若将抽象工厂模式认为是汽车配件生产工厂,生产一个产品族的产品,轮胎、方向盘、发动机等。那么建造者模式就是汽车组装工厂,通过对部件的组装返回一辆完整汽车,宝马、奔驰、保时捷。

亲爱的读者朋友,不知不觉今天的分享就又结束了。你有什么想和我说的吗?

感谢陪伴,感谢阅读。

设计模式相关 demo 代码,在这个码云链接:

https://gitee.com/JeffBro/DesignPattern23

表情包来源于网络,侵删。

「子乾建建为作者笔名」

0 人点赞