前言
建造者模式的原始定义如下:The intent of Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
简单的说就是,构造函数传入太多参数了,看着不舒服,所有要模式化。
主要的分工如下:
1,产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。
2,抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个
返回复杂产品的方法 getResult()。
3,具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4,指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,
在指挥者中不涉及具体产品的信息。
引用:[模式的分工](http://c.biancheng.net/view/1354.html)
本例背景阐述
当针对一个问题(如肺结节)看了很多论文的时候,就会发现很多雷同的论文(实现的过程一样,但是有一个或多个步骤不一样),如果想把雷同的论文实现都复现一遍,有两种方式可以实现,构造函数传入多个参数(简单方法)、建造者模式(复杂和高大上方法)。
插播问题:请问谁看了很多论文?
本篇以肺结节检测流程为背景,阐述如何用建造者模式将不同论文的实现放在一个框架中。
假定肺结节检测过程分为三个步骤:图像预处理、肺叶区域分割和肺结节检出。
建造者模式的代码实现
首先定义了多个抽象类,用于约束后续的实现。将这些抽象类统一放在同一个文件。
#pragma region // 抽象类模块
class Preprocessing
{
public:
virtual void print_name()=0;
virtual ~Preprocessing(){};
};
class Segment
{
public:
virtual void print_name()=0;
virtual ~Segment(){};
};
class Detection
{
public:
virtual void print_name()=0;
virtual ~Detection(){};
};
class LesionDetection
{
public:
virtual void set_preprocessing(Preprocessing* preprocessing) = 0;
virtual void set_segment(Segment* segment)=0;
virtual void set_detection(Detection* detection)=0;
virtual void get_report()=0;
virtual ~LesionDetection(){};
};
class Builder
{
public:
virtual ~Builder(){};
virtual void build_preprocessing()=0;
virtual void build_segment()=0;
virtual void build_detection()=0;
virtual LesionDetection* get_alg()=0;
};
#pragma endregion
本实例中,Product就是肺结节检测,继承于抽象类LesionDetetion。
#pragma region // 肺结节检测
class Nodule: public LesionDetection
{
public:
Nodule():m_preprocessing(nullptr),m_detection(nullptr),m_segment(nullptr){}
void set_preprocessing(Preprocessing* preprocessing){
m_preprocessing = preprocessing;
}
void set_segment(Segment* segment){
m_segment = segment;
}
void set_detection(Detection* detection){
m_detection = detection;
}
void get_report(){
cout<<"The pipeline of nodule including:"<<endl;
if(m_preprocessing){
m_preprocessing->print_name();
}
if(m_segment){
m_segment->print_name();
}
if(m_detection){
m_detection->print_name();
}
}
~Nodule(){
if(m_preprocessing){
delete m_preprocessing;
}
if(m_segment){
delete m_segment;
}
if(m_detection){
delete m_detection;
}
}
private:
Preprocessing* m_preprocessing;
Segment* m_segment;
Detection* m_detection;
};
#pragma endregion
指挥者的的作用就是用于更上一层的组装并输出具体的产品(肺结节类)。这里的construct 的返回类型到底时void 还是product,其实是有争议的。有很多实现过程中,觉得返回void更加合适,因为指挥者只是负责组装零件,不需要和具体的产品有关联,而产品的获取则直接从builder类获取,这样的实现耦合性更低。但是,我个人比较喜欢直接返回产品,因为这种设计在客户端调用的时候有更低的出错率,毕竟只调用一个命令就可以实现组装并返回产品要比调用两个命令更简单,这就是原则里的最小接口原则。
#pragma region // 指挥者
class Director
{
public:
Director(Builder* builder): m_builder(builder){}
LesionDetection* construct(){
m_builder->build_preprocessing();
m_builder->build_segment();
m_builder->build_detection();
return m_builder->get_alg();
}
private:
Builder* m_builder;
};
#pragma endregion
至此,建造者的框架就已经搭建好了,接下来就是针对不同的论文进行代码实现。本例中假定了两篇论文的实现。如下是学者wang的论文方法。
#pragma region // 论文 wang 的实现(具体建造者)
class MiddlePreprocessing: public Preprocessing
{
void print_name()
{
cout<<"Using middle filter to Preprocessing."<<endl;
}
};
class ThresholdSegment: public Segment
{
void print_name()
{
cout<<"Using Threshold to Segment."<<endl;
}
};
class SiftDetection: public Detection
{
void print_name()
{
cout<<"Using sift to Detection."<<endl;
}
};
class PaperWang :public Builder
{
public:
PaperWang(){
m_nodule = new Nodule();
}
void build_preprocessing(){
m_nodule->set_preprocessing(new MiddlePreprocessing());
}
void build_segment(){
m_nodule->set_segment(new ThresholdSegment());
}
void build_detection(){
m_nodule->set_detection(new SiftDetection());
}
LesionDetection* get_alg(){
return m_nodule;
}
private:
Nodule* m_nodule;
};
#pragma endregion
如下是学者李的实现方法。
#pragma region // 论文 li 的实现(具体建造者)
class MeanPreprocessing:public Preprocessing
{
void print_name()
{
cout<<"Using mean filter to Preprocessing."<<endl;
}
};
class GrowingSegment: public Segment
{
void print_name()
{
cout<<"Using Growing to Segment"<<endl;
}
};
class BoostDetection: public Detection
{
void print_name()
{
cout <<"Using Boost to Detection."<<endl;
}
};
class PaperLi: public Builder
{
public:
PaperLi(){
m_nodule = new Nodule();
}
void build_preprocessing(){
m_nodule->set_preprocessing(new MeanPreprocessing());
}
void build_segment(){
m_nodule->set_segment(new GrowingSegment());
}
void build_detection(){
m_nodule->set_detection(new BoostDetection());
}
LesionDetection* get_alg(){
return m_nodule;
}
private:
Nodule* m_nodule;
};
#pragma endregion
客户端调用测试代码如下
int main(int argc, char const *argv[])
{
// 实际应用中,只需要修改这行代码,就可以切换不同的论文(具体建造者)实现.
Builder* builder = new PaperWang();
Director* director = new Director(builder);
LesionDetection* lesion = director->construct();
lesion->get_report();
cout<<"\n\nChange paper..."<<endl;
builder = new PaperLi();
director = new Director(builder);
lesion = director->construct();
lesion->get_report();
delete builder;
delete director;
delete lesion;
return 0;
}
输出结果如下:
$ ./builder.exe
The pipeline of nodule including:
Using middle filter to Preprocessing.
Using Threshold to Segment.
Using sift to Detection.
Change paper...
The pipeline of nodule including:
Using mean filter to Preprocessing.
Using Growing to Segment
Using Boost to Detection.
总结
建造者模式搭建起来比较繁琐,但是搭建之后,往里面添加论文的时候,逻辑性和条理性比较清晰。