【设计模式】建造器模式(Builder Pattern)

在这里插入图片描述

 

🔥 核心

通过建造器,使用多个简单的对象一步一步构造出一个复杂的对象。

 

🙁 问题场景

你现在从一名程序开发者转行为了一名房屋建筑师。你的任务就是建房子。

你很快建好了一个 房子(House) 。这个房子普普通通,压根不是什么难事儿。很快,你收到了越来越多的订单,房子好像愈加复杂了起来:

一个 带花园的房子(HouseWithGarden)
一个 带车库的房子(HouseWithGarage)
一个 带泳池的房子(HouseWithSwimmingPool)
一个 带火箭基地的的房子(HouseWithRocketBase)
一个 既带花园又带泳池的房子(HouseWithGardenAndSwimmingPool)
一个 既带车库又带泳池又带火箭基地的房子(HouseWithGardenAndSwimmingPoolAndRocketBase)

于是,你以 房子 为基类 ,派生出所有各种各样的房子的子类…等等,房子的种类实在是太多了,为每一种房子创建一个子类显然是愚蠢的行为。

你立即发现了所谓房子的本质——无非是 房子本身花园车库泳池火箭基地 的组合。所以,你可以使用一个包含所有可能参数的超级构造函数。使用超级构造函数后,的确避免了生成子类,但是这使得对于构造函数的调用十分不简洁:

new House(3, 2, 4, true, false, true, true, true, false, false, true, false, false, true, true, false);

还有什么其他的注意吗?

 

🙂 解决方案

你突然意识到,对于这种复杂对象的构造,是不可能一步到位的。

也就是说,必须使用多个简单的对象一步一步构造出一个复杂的对象。复杂对象的整个构造过程,被拆分为一个个小步骤,然后在建造器中调用一个个小步骤。

很快,你写好了以下的小步骤:

建造花园;
建造车库;
建造泳池;
建造火箭基地…

对于 既带花园又带泳池的房子 ,就可以调用 建造花园 + 建造泳池。

对于 既带车库又带泳池又带火箭基地的房子,就可以调用 建造车库 + 建造车库 + 建造火箭基地。

对于一种房子,你仅仅需要调用它需要的一组步骤即可!

 

🌈 有趣的例子

建造者模式随处可见。在餐厅里,有许多食物(蛋糕(Cake)牛肉(Beef))和饮品(咖啡(Coffee)红酒(Redwine))。它们都继承了 单品(Item) 接口。

餐厅提供了多种 套餐(Meal) 。使用建造器,就能组合出这些套餐:

1)蛋糕 + 咖啡

2)牛肉 + 红酒

在这里插入图片描述

 单品接口
interface Item {
    String getName();
    int getPrice();
}

 蛋糕类
class Cake implements Item {

    @Override
    public String getName() {
        return "我是一块蛋糕";
    }

    @Override
    public int getPrice() {
        return 12;
    }
}

 牛肉类
class Beef implements Item {

    @Override
    public String getName() {
        return "我是一份牛肉";
    }

    @Override
    public int getPrice() {
        return 80;
    }
}

 咖啡类
class Coffee implements Item {

    @Override
    public String getName() {
        return "我是一杯咖啡";
    }

    @Override
    public int getPrice() {
        return 30;
    }
}

 红酒类
class RedWine implements Item {

    @Override
    public String getName() {
        return "我是一瓶红酒";
    }

    @Override
    public int getPrice() {
        return 400;
    }
}

 一顿正餐
 通常是单品组合成的套餐
class Meal {

    private List<Item> items = new ArrayList<>();

    public void addItem(Item item) {
        items.add(item);
    }

    public void showItems() {
        for (Item item : items) {
            System.out.println(item.getName());
        }
    }

    public void showTotalPrice() {
        int total = 0;
        for (Item item : items) {
            total += item.getPrice();
        }
        System.out.println(total);
    }
}

 套餐建造器
class MealBuilder {

    public Meal prepareMeal1() {
        Meal meal = new Meal();
        meal.addItem(new Cake());
        meal.addItem(new Coffee());
        return meal;
    }

    public  Meal prepareMeal2() {
        Meal meal = new Meal();
        meal.addItem(new Beef());
        meal.addItem(new RedWine());
        return meal;
    }
}
public class BuilderPatternDemo {
    public static void main(String[] args) {

        // 新建一个正餐建造器
        MealBuilder mealBuilder = new MealBuilder();

        // 构造套餐1
        Meal meal1 = mealBuilder.prepareMeal1();
        // 展示一下套餐
        meal1.showItems();
        meal1.showTotalPrice();

        // 构造套餐2
        Meal meal2 = mealBuilder.prepareMeal2();
        // 展示一下套餐
        meal2.showItems();
        meal2.showTotalPrice();
    }
}
我是一块蛋糕
我是一杯咖啡
42
我是一份牛肉
我是一瓶红酒
480

 

☘️ 使用场景

◾️使用建造器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。

假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便;因此,你需要重载这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。

class Pizza {
Pizza(int size) { … }
Pizza(int size, boolean cheese) { … }
Pizza(int size, boolean cheese, boolean pepperoni) { … }
// …

只有在 C# 或 Java 等支持方法重载的编程语言中才能写出如此复杂的构造函数。

建造器模式让你可以分步骤构造对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。

◾️当你希望使用代码创建不同形式的产品(例如石头或木头房屋)时,可使用建造器模式。

如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用建造器模式。

基本建造器接口中定义了所有可能的制造步骤,具体建造器将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。

◾️使用建造器构造组合树或其他复杂对象。

建造器模式让你能分步骤构造产品。你可以延迟执行某些步骤而不会影响最终产品。你甚至可以递归调用这些步骤,这在创建对象树时非常方便。

建造器在执行制造步骤时,不能对外发布未完成的产品。这可以避免客户端代码获取到不完整结果对象的情况。

 

🧊 实现方式

(1)清晰地定义通用步骤,确保它们可以制造所有形式的产品。否则你将无法进一步实施该模式。

(2)在基本建造器接口中声明这些步骤。

(3)为每个形式的产品创建具体建造器类,并实现其构造步骤。

(4)考虑创建主管类。它可以使用同一建造器对象来封装多种构造产品的方式。

(5)客户端代码会同时创建建造器和主管对象。构造开始前,客户端必须将建造器对象传递给主管对象。通常情况下,客户端只需调用主管类构造函数一次即可。主管类使用建造器对象完成后续所有制造任务。还有另一种方式,那就是客户端可以将建造器对象直接传递给主管类的制造方法。

(6)只有在所有产品都遵循相同接口的情况下,构造结果可以直接通过主管类获取。否则,客户端应当通过建造器获取构造结果。

 

🎲 优缺点

  ➕ 你可以分步创建对象,暂缓创建步骤或递归运行创建步骤。

  ➕ 生成不同形式的产品时,你可以复用相同的构造代码。

  ➕ 单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。

  ➖ 产品必须有共同点,从而有相同的构造子步骤。

  ➖ 如果内部变化复杂,会有很多的建造类。

  ➖ 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。

 

🌸 补充

 上面提到了一个没有详细讲解的概念:主管类(Director)。

 主管类位于建造器和调用者之间,用于对建造器再进行一次复杂的组合;同时,它也使得建造器的细节对调用者完全隐藏。另外,主管类不是必须的。

 

🔗 参考网站

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值