概述建造者模式的结构
-
建造者(Builder):
-
每个建造者负责构建一个对象的某一部分,或者在某些情况下,负责构建整个对象。每个建造者可以有自己特定的逻辑,用于设置不同的参数和属性。这种方式让每个建造者关注于其特定的部分,有助于逻辑的简化和清晰化。
-
-
产品(Product):
-
最终构建出的复杂对象(如汽车)就是产品。产品可能有许多属性和状态,这些属性可以通过不同的建造者来设置。
-
-
指挥者(Director):
-
指挥者的角色是协调不同的建造者,按照一定的顺序调用它们的方法,以完成复杂对象的构建。指挥者提供了一个高层的接口,客户端不需要关心具体的构建步骤,只需调用指挥者的相关方法。
-
工作流程
-
客户端通过指挥者请求构建一个复杂对象。指挥者会选择一个合适的建造者。
-
指挥者控制建造过程,逐步调用建造者的方法来构建对象的不同部分。
-
最后,指挥者返回完整的构建好的对象。
优势
-
清晰的责任分离:构建步骤和具体实现分开,职责明确。
-
灵活性和可扩展性:可以方便地添加新的建造者来扩展构建新类型的对象。
-
代码易于维护:构建过程和构建细节分散,使得代码的可读性好、逻辑更简洁。
建造者模式实际上通过模块化构建过程,确保了每个部分的构建都能有序进行,同时将复杂对象的构建过程进行简化和清晰化。你可以将其视作一种工厂模式的变体,但着重于逐步构建复杂对象,而不是一次性创建。这样一来,整个系统的可维护性、可读性和扩展性都得到了显著提升。
我的例子
首先放一下类图:
(笔者第一次画类图,有问题和错误请指出)
#include <iostream>
#include <string.h>
using namespace std;
/*原始基类,套餐的结构*/
class Meal
{
public:
// 获取饮料
string getDrink()
{
return drink;
}
// 获取汉堡
string getBurger()
{
return burger;
}
// 获取薯条
string getFries()
{
return fries;
}
// 设置饮料
void setDrink(const string& drink)
{
this->drink = drink;
}
// 设置汉堡
void setBurger(const string& burger)
{
this->burger = burger;
}
// 设置薯条
void setFries(const string& fries)
{
this->fries = fries;
}
// 重载输出流运算符,格式化输出套餐内容
friend ostream& operator<<(ostream& os, const Meal& that)
{
os << "套餐内容:" << endl;
os << " " << that.drink << endl;
os << " " << that.burger << endl;
os << " " << that.fries << endl;
return os;
}
private:
string burger; // 汉堡
string fries; // 薯条
string drink; // 饮料
};
/*抽象父类,抽象建造者*/
class MealBuilder
{
protected:
Meal* meal; // 套餐指针
public:
MealBuilder() {}
// 建造饮料的纯虚函数
virtual void builderDrink() = 0;
// 建造汉堡的纯虚函数
virtual void builderBurger() = 0;
// 建造薯条的纯虚函数
virtual void builderFries() = 0;
// 创建套餐
void createMeal()
{
meal = new Meal();
}
// 获取套餐
Meal* getMeal()
{
return meal;
}
};
// 鸡肉套餐建造者
class ChickenMealBuilder : public MealBuilder
{
public:
ChickenMealBuilder() {}
// 建造鸡肉套餐饮料
void builderDrink()override
{
meal->setDrink("可口可乐");
}
// 建造鸡肉堡
void builderBurger()override
{
meal->setBurger("鸡肉堡");
}
// 建造特大份薯条
void builderFries() override
{
meal->setFries("特大份薯条");
}
};
// 牛肉套餐建造者
class BeefBurgerBuilder :public MealBuilder
{
public:
BeefBurgerBuilder() {}
// 建造芬达饮料
void builderDrink() override
{
meal->setDrink("芬达");
}
// 建造牛肉汉堡
void builderBurger() override
{
meal->setBurger("牛肉汉堡");
}
// 建造薯块
void builderFries() override
{
meal->setFries("薯块");
}
};
// 虾肉套餐建造者
class ShrimpMealBuilder :public MealBuilder
{
public:
ShrimpMealBuilder() {}
// 建造果粒橙饮料
void builderDrink() override
{
meal->setDrink("果粒橙");
}
// 建造虾肉汉堡
void builderBurger() override
{
meal->setBurger("虾肉汉堡");
}
// 建造虾条
void builderFries() override
{
meal->setFries("虾条");
}
};
// 套餐构建导演
class MealDirector
{
private:
MealBuilder* mealBuilder; // 套餐建造者指针
public:
MealDirector(MealBuilder* builder) :mealBuilder(builder) {}
// 构建套餐
Meal* construct()
{
mealBuilder->createMeal();
mealBuilder->builderDrink();
mealBuilder->builderBurger();
mealBuilder->builderFries();
return mealBuilder->getMeal();
}
};
int main(int argc, const char* argv[])
{
/*牛肉套餐*/
MealBuilder* beefBuilder = new BeefBurgerBuilder();
MealDirector beefDirector(beefBuilder);
Meal* beef = beefDirector.construct();
cout << *beef << endl;
/*鸡肉套餐*/
MealBuilder* chickenBuilder = new ChickenMealBuilder();
MealDirector chickenDirector(chickenBuilder);
Meal* chicken = chickenDirector.construct();
cout << *chicken << endl;
/*虾肉套餐*/
MealBuilder* shrimpBuilder = new ShrimpMealBuilder();
MealDirector shrimpDirector(shrimpBuilder);
Meal* shrimp = shrimpDirector.construct();
cout << *shrimp << endl;
// 释放资源
delete beefBuilder;
delete chickenBuilder;
delete shrimpBuilder;
return 0;
}
例子介绍
这个例子实现了一个用餐套餐构建系统,使用了建造者模式,整体设计清晰且结构合理。但是非常粗糙,仅供参考理解建造者模式,下面是一些我自己总结的缺点和改进建议:
缺点:
-
动态内存管理:目前在
MealBuilder
中通过new
动态分配的Meal
对象没有得到适当地释放,这可能会导致内存泄漏。在main
中没有delete
相应的Meal
对象。 -
缺少智能指针:使用原始指针(如
Meal* meal
)较难管理生命周期,容易导致内存泄漏或悬挂指针。建议使用std::unique_ptr
或std::shared_ptr
来更安全地管理内存。 -
构建过程的灵活性不足:当前,每个套餐的构建过程是固定的,不能自由组合不同的食材。如果需要更灵活的组合,建造者模式将显得有些不适合。
-
缺少析构函数:如果使用了 raw pointer,建议在使用
MealBuilder
时添加析构函数以确保资源的释放,或将其变成一个智能指针以确保自动释放。
改进建议:
-
内存管理的改进:
-
将
Meal* meal;
改为std::unique_ptr<Meal> meal;
,这样可以自动管理内存。 -
在
MealDirector
的析构函数中也可以添加删除逻辑,确保不留内存泄漏。
-
-
构建过程的灵活性:
-
可以考虑设计一个更通用的接口,使得在构建套餐时支持自由组合。例如,增加添加食材的方法,使得套餐的每个部分可以单独选择。
-
-
输入参数处理:
-
可以考虑以参数的形式传递套餐的组成部分,从而提升灵活性并减少成员函数数量。
-
-
使用现代C++特性:
-
使用
std::array
或std::vector
来管理多项套餐,使得管理多个产品组件更为高效。 -
加强使用栈而非堆来管理简单对象(如
Meal
),然后在构造结束时将其返回以减少不必要的动态分配。
-
应用场景
建造者模式在许多实际应用场景中非常有效,特别是在需要构建复杂对象的情况下。以下是一些具体的应用场景:
1. 餐厅点餐系统
在餐厅点餐时,顾客可以根据自己的需求逐步选择饮料、主菜和配菜。建造者模式能够将这个过程分为不同的建造者类,使得每种套餐的构建都非常清晰。
2. 文档生成
在需要生成复杂文档(如报告或邮件)时,可以使用建造者模式。有些文档可能由标题、摘要、正文和附录组成,使用不同的建造者可以控制各个组成部分的格式和内容。
3. 图形用户界面(GUI)
在构建用户界面时,不同的界面元素(如按钮、文本框和菜单)可以通过不同的建造者进行组合。每个建造者负责生成特定类型的界面组件,这样可以灵活地组合和重用。
4. 配置文件生成
在创建复杂的配置文件或对象时,建造者模式可以提供清晰的构建步骤。用户可以通过选择不同的参数和选项来灵活构造配置,而不必关注其底层实现。
5. 游戏角色创建
在游戏开发中,角色创建通常涉及多个属性(如健康值、攻击力、技能等)的组合。每个角色的建造者可以负责定义一组属性和外观,从而灵活构建不同类型的角色。
6. 数据库查询构建
在处理复杂的数据库查询时,建造者模式可以用来逐步组合查询的不同部分(如选择字段、过滤条件、排序等),从而生成最终的 SQL 查询字符串。
7. 复杂对象的配置
例如,在软件配置或安装程序中,需要指定多个选项和参数(如服务器设置、安装路径、必要的依赖项等),建造者模式能够提供用户友好的配置过程。
8. 网络请求构建
在发起复杂的网络请求时,建造者模式可以用来逐步设置请求的各个方面,如请求方法、URL、请求头和请求体。这种方式可以提高请求构建的可读性和易用性