简介
当构建一个复杂对象时,将构建过程与表示分离。使得同样的过程创建不同的对象。生成器与其他创建型模式不同, 生成器不要求产品拥有通用接口。 这使得用相同的创建过程生成不同的产品成为可能。生成器方法通常支持方法链 (例如 someBuilder->setValueA(1)->setValueB(2)->create() ),来组成复杂的对象。相比于工厂模式专门用于生产一系列相关对象而言,生成器重点关注如何分步生成复杂对象。 这也是我们常用创建模式的最后一节,后续将会带来结构模式的讲解。
生成器UML介绍
-
生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤,一般都由虚函数组成。
-
具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
-
产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
-
主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
-
客户端 (Client) 必须将某个生成器对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。
我们可以看到生成器执行了单一职责原则,可以将复杂构造代码从产品的业务逻辑中分离出来。和工厂模式类似的是,生成器模式也是采用来生成器和具体生成器来构建不同实现。但是不同的是工厂模式是通过**工厂来对具体操作统一管理**,但是在生成器中不同的设计了主管(Director)来构建步骤顺序。<br /> 对于软件设计,我们需要明确的是:主函数的代码尽量减少实例初始化的动作,将初始化的行为尽量封装起来供主函数调用。主函数大多为业务逻辑,过多的代码会影响美观及维护。
生成器模式模板
可以说用生成器模式来作为整套软件的框架+工厂模式作为不同方法的可选组件的方式即可以组成一套可拓展性较为良好的框架了。<br /> 对于生成器而言,不存在和单例模式和工厂模式一样万用的函数通用模板,这里给出一套比较全面的生成器模式模板。
// 拿到的产品类
class Product1{
public:
std::vector<std::string> parts_;
void ListParts()const{
std::cout << "Product parts: ";
for (size_t i=0;i<parts_.size();i++){
if(parts_[i]== parts_.back()){
std::cout << parts_[i];
}else{
std::cout << parts_[i] << ", ";
}
}
std::cout << "\n\n";
}
};
// 生成器,一般存放一些通用的产品步骤
class Builder{
public:
virtual ~Builder(){}
virtual void ProducePartA() const =0;
virtual void ProducePartB() const =0;
virtual void ProducePartC() const =0;
};
// 具体生成器,可以看到上述虚函数的实例化
class ConcreteBuilder1 : public Builder{
private:
Product1* product;
public:
ConcreteBuilder1(){
this->Reset();
}
~ConcreteBuilder1(){
delete product;
}
void Reset(){
this->product= new Product1();
}
/**
* 所有生产步骤都使用同一个产品实例
*/
void ProducePartA()const override{
this->product->parts_.push_back("PartA1");
}
void ProducePartB()const override{
this->product->parts_.push_back("PartB1");
}
void ProducePartC()const override{
this->product->parts_.push_back("PartC1");
}
// 通过GetProduct 拿到不同步骤的执行的结果
Product1* GetProduct() {
Product1* result= this->product;
this->Reset();
return result;
}
};
// 定义了不同的生成方式
class Director{
/**
* @var Builder
*/
private:decltype
Builder* builder;
public:
void set_builder(Builder* builder){
this->builder=builder;
}
void BuildMinimalViableProduct(){
this->builder->ProducePartA();
}
void BuildFullFeaturedProduct(){
this->builder->ProducePartA();
this->builder->ProducePartB();
this->builder->ProducePartC();
}
};
// client 可以放在一个函数中,也可以写在main函数中
void ClientCode(Director& director)
{
ConcreteBuilder1* builder = new ConcreteBuilder1();
director.set_builder(builder);
std::cout << "Standard basic product:\n";
director.BuildMinimalViableProduct();
Product1* p= builder->GetProduct();
p->ListParts();
delete p;
std::cout << "Standard full featured product:\n";
director.BuildFullFeaturedProduct();
p= builder->GetProduct();
p->ListParts();
delete p;
// 当然构建器模式也可以在没有Director类的情况下使用。
std::cout << "Custom product:\n";
builder->ProducePartA();
builder->ProducePartC();
p=builder->GetProduct();
p->ListParts();
delete p;
delete builder;
}
int main(){
Director* director= new Director();
ClientCode(*director);
delete director;
return 0;
}
从引用类到引用函数
我们可以发现在生成器中使用到了类之间参数的传递,而如何通过函数指向指针,来设计应用函数,这里需要说一下。下面的例子为SeqList模板类的操作
constexpr int kMathEpsilon = 0;
template <class ElemType>
SeqList<ElemType>::SeqList(ElemType v[], int n, int size) //构造函数
{
elems = new ElemType[size];
assert(elems);
maxLength = size;
length = n;
for (int i = 0; i < length; i++)
elems[i] = v[i];
}
// 函数作为参数传入,一般使用函数指针
// https://www.jianshu.com/p/974540146931
// int *func(int a, int b) ; //这个声明的是一个函数
// int (*func)(int a, int b); //这个声明的是一个指针变量
template <class ElemType>
void SeqList<ElemType>::Traverse(void (*visit)(const ElemType &)) const//引用函数,函数指向指针
{
for(int i=0;i<length;i++)
{
visit(elems[i]);
}
}
template <class ElemType>
void Write(const ElemType &e)
{
// 用decltype
// https://blog.csdn.net/u014609638/article/details/106987131/
// 声明了一个函数类型
//using FuncType = int(int &, const ElemType &);
//FuncType *pf = add_to;
decltype(add_to) *pf = add_to;
pf(2, e);
}
int add_to(int &des, const ElemType &e)
{
cout << e << " ";
}
void main() {
int a[]={1,2,3};
SeqList<int> la(a,3,5);
la.Traverse(Write);
}