您现在的位置是:首页 > 正文

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

2024-01-30 22:39:02阅读 0

在这里插入图片描述

 

🔥 核心

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

 

🙁 问题场景

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

你很快建好了一个 房子(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)。

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

 

🔗 参考网站

网站文章

  • oracle网络访问权限,ORACLE的网络配置,与权限初步

    1、服务器1.1常用工具emnetmgrnetca1.2相关配置文件listener.oratnsnames.oraC:/oracle/product/10.2.0/db_1/NETWORK/ADMI...

    2024-01-30 22:38:57
  • 08 - 安装脚本Section - [Setup]

    安装脚本Section[Setup] section此section包含安装程序和卸载程序使用的全局设置。您创建的任何安装都需要包含指令。这是[Setup]的示例:[Setup]AppName=My ...

    2024-01-30 22:38:49
  • 文件上传-.user.ini的妙用

    文件上传-.user.ini的妙用

    文件上传漏洞-.user.ini在文件上传中的妙用

    2024-01-30 22:38:42
  • 数组过滤c语言,将NSArray过滤到Objective-C中的新NSArray中

    有很多方法可以做到这一点,但到目前为止,最肯定的方法是使用[NSPredicate predicateWithBlock:]:NSArray *filteredArray = [array filte...

    2024-01-30 22:38:13
  • SVN上传文件

    SVN上传文件

    SVN使用技巧

    2024-01-30 22:38:05
  • MongoDB模糊查询($regex查询、正则表达式匹配查询) 热门推荐

    MongoDB模糊查询($regex查询、正则表达式匹配查询) 热门推荐

    MongoDB的模糊查询可以使用 $regex 运算符通过正则表达式来进行匹配查询。 $regex :为查询中的模式匹配字符串提供正则表达式功能 。 语法: { &lt; field &gt;: { $ regex : / pattern / , $ options : ‘’ } } { &lt; field &gt;: { $ regex : ‘pattern’ , $ optio...

    2024-01-30 22:37:58
  • linux中命令tat,照着书敲linux下载安装命令?大汇总来咯!!!

    linux中命令tat,照着书敲linux下载安装命令?大汇总来咯!!!

    linux下载安装的命令一. 本地上传1.1 使用scp命令1.2 使用xshell工具1.3 常用方法二. 网络远程下载2.1 curl_一种下载文件的工具2.2 wget_软件下载工具(非安装方式...

    2024-01-30 22:37:29
  • IOS中的多线程和NSRunLoop概述(转载)

    IOS中的多线程和NSRunLoop概述(转载)

    线程概述  有些程序是一条直线,从起点到终点,如Hello World,运行打印完,它的生命周期便结束了;有些程序是一个圆,不断循环,直到将它切断,如操作系统,一直运行直到你关机。  一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时, 一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他...

    2024-01-30 22:37:20
  • C# - Excel - Microsoft Access 数据库引擎找不到对象

    我几乎要无语了,疯掉了,以为是office本身的问题,换了好多次office2007,安装又不顺利,换到了office2010,想想大部分应该兼容2007,所以用着office2010了. 甚至差点要...

    2024-01-30 22:37:13
  • IDEA 设置Kotlin 自动类型推断后的类型

    IDEA 设置Kotlin 自动类型推断后的类型

    开发kotlin类型大部分都是类型推断出来的,为了编码的规范,防止因为类型问题出现bug,可以设置类型推断的显示。设置完成后显示为这样

    2024-01-30 22:36:44