建造者模式
Builder模式, 中文翻译为建造者模式或者构建者模式, 也有人叫它生成器模式. 也是比较常用的创建型设计模式.
本身代码的原理和实现掌握起来不难, 难点在于他的应用场景.
下面可以围绕这两个问题进行了解
- 既然可以直接用构造函数 或者使用set函数设置参数进行构建对象, 为什么还需要使用建造者进行对象的构建
- 建造者模式和工厂模式都可以创建对象,那它们两个的区别在哪里
介绍
建造者模式将包含多个部件的对象的构建过程进行剥离, 客户只需要给出他需要的部件类型, 不用关心如何构建, 以及构建的细节.
比如你,我都需要造一间房子, 造房子的过程无非是从打地基, 筑墙, 装修等过程, 但是最后你, 我期望的房子的风格肯定是不一样的, 最终建造的房子肯定呈现不同风格.
问题1. 为什么使用建造者
平常开发中, 最常用new 构造出一个对象.
假设 这里有个一个类 House
#define DEFAULT_ROOF "default roof"
#define DEFAULT_WALL "default wall"
#define DEFAULT_FLOOR "default floor"
class House {
private:
std::string roof_;
std::string wall_;
std::string floor_;
public:
House(std::string roof, std::string wall, std::string floor) {
if (roof.empty()) {
roof = DEFAULT_ROOF;
}
if (wall.empty()) {
wall = DEFAULT_WALL;
}
if (floor.empty()) {
floor = DEFAULT_FLOOR;
}
roof_ = roof;
wall_ = wall;
floor_ = floor;
}
};
给空字符串表示使用默认风格的装饰. 现在只有三个选项, 但如果需要增加其他的装修什么吊灯, 沙发啥的… 参数逐渐增多,变成了8个、10个,甚至更多. 如果继续沿用现在的风格, 构造函数只会变得更长, 可读性和易用性就会变差.
这个时候就会想到定义set变量的函数, 你需要用哪个就自己设这个变量
#define DEFAULT_ROOF "default roof"
#define DEFAULT_WALL "default wall"
#define DEFAULT_FLOOR "default floor"
class House {
private:
std::string roof_;
std::string wall_;
std::string floor_;
public:
House() {}
void setRoof(std::string roof) { roof_ = roof;}
void setWall(std::string wall) { wall_ = wall;}
void setFloor(std::string floor) { floor_ = floor;}
};
到现在仍未使用 建造者模式, 但可以再想下以下场景是否能够适配
- 假设有一些必填项, 比如大小, 空间, 占地, 门牌… 这些需要放到构造函数中强制填入, 那参数列表又会很长. 但如果使用set 函数, 没地方进行校验.
- 假设大小, 占地这些参数有关联和约束, 也没有地方进行校验.
- 假设房子造完了, 我不期望他的属性进行修改, 那就不能暴露他的 set函数
这时候可以使用建造者
我们可以把校验逻辑放置到Builder类中,先创建建造者,并且通过set()方法设置建造者的变量值,然后在使用build()方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
当然我们也可以将Builder类设计成独立的非内部类
以下代码隐藏了 house的set 函数, 并在House::Builder::build中进行了校验
#define DEFAULT_ROOF "default roof"
#define DEFAULT_WALL "default wall"
#define DEFAULT_FLOOR "default floor"
class House {
private:
std::string roof_;
std::string wall_;
std::string floor_;
House() {}
void setRoof(std::string roof) { roof_ = roof;}
void setWall(std::string wall) { wall_ = wall;}
void setFloor(std::string floor) { floor_ = floor;}
public:
void print() {
std::cout << "roof : " << roof_ << std::endl;
std::cout << "wall : " << wall_ << std::endl;
std::cout << "floor : " << floor_ << std::endl;
}
class Builder {
private:
std::string roof_;
std::string wall_;
std::string floor_;
public:
Builder() {}
~Builder() {}
void setRoof(std::string roof) { roof_ = roof; }
void setWall(std::string wall) { wall_ = wall; }
void setFloor(std::string floor) { floor_ = floor; }
House* build() {
if (roof_.empty()) {
roof_ = DEFAULT_ROOF;
}
if (wall_.empty()) {
wall_ = DEFAULT_WALL;
}
if (floor_.empty()) {
floor_ = DEFAULT_FLOOR;
}
House *house_ = new House();
house_->setRoof(roof_);
house_->setWall(wall_);
house_->setFloor(floor_);
return house_;
}
};
};
客户使用方式
int main() {
House::Builder builder;
builder.setFloor("red floor");
auto* house = builder.build();
house->print();
return 0;
}
效果
./bin/design/Builder
roof : default roof
wall : default wall
floor : red floor
问题2. 与工厂模式有何区别?
工厂模式是用来创建不同但是相关类型的对象, 由给定的参数来决定创建哪种类型的对象. 建造者模式是用来创建一种类型的复杂对象, 通过设置不同的可选参数, “定制化”地创建不同的对象.
网上有一个经典的例子很好地解释了两者的区别.
顾客走进一家餐馆点餐, 我们利用工厂模式, 根据用户不同的选择, 来制作不同的食物, 比如披萨、汉堡、沙拉. 对于披萨来说, 用户又有各种配料可以定制, 比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨.
优点
- 将构建过程进行剥离, 客户无需关心建造的细节. 使用同样的建造过程创建不同风格的对象.
- 如果将建造者放到类外实现, 可以实现多个建造者建造不同风格的house, 这些建造者相互独立, 无依赖.
缺点
- 创建的产品一般具有较多的共同点, 如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
适用
- 需要生成的产品对象有复杂的内部结构(通常包含多个成员变量);
- 产品对象内部属性有一定的生成顺序;
- 同一个创建流程适用于多种不同的产品。
代码