设计模式-04 设计模式-Builder
1.定义
建造者模式(Builder Pattern)是一种创建型设计模式,它允许你使用不同的构建步骤来创建复杂的对象。
建造者模式的定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
指挥者|/ \\/ \\/ \\具体建造者 A 具体建造者 B| |/ |_______________/_______________|______________| || || |_______|______________________________________________|____| || 已构建的产品 ||__________________________________________________________|
在这个 ASCII 图中:
- 指挥者负责协调建造过程,它告诉建造者要创建哪些部分以及如何组装它们。
- 具体建造者代表不同的产品类型或变体。每个具体建造者负责构建产品的一个或多个部分。
- 已构建的产品是建造过程的结果。
建造过程如下:
- 指挥者创建一个具体建造者。
- 指挥者调用具体建造者的构建方法来创建产品的不同部分。
- 指挥者可以调用多个具体建造者来创建复杂的产品结构。
- 一旦所有部分都构建完成,指挥者调用具体建造者的获取产品方法来检索已构建的产品。
2.内涵
换句话说,建造者模式将对象的构建过程与对象的表示分离。这使得你可以使用相同的构建过程来创建不同类型的对象,而无需更改构建代码本身。
建造者模式包含以下角色:
- 建造者(Builder):负责创建和组装最终的对象。
- 具体建造者(Concrete Builder):实现建造者接口,并负责创建和组装最终对象的特定部分。
- 指挥者(Director):负责协调建造过程,它告诉建造者要创建哪些部分以及如何组装它们。
- 产品(Product):最终创建的对象。
3.案例对比
不用 Builder 设计模式
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include <tuple>
#include <sstream>
#include <memory>
using namespace std;/*** <p>hello</p><ul> <li>hello</li> <li>world</li></ul>* @return*/
int main() {auto text= "hello";string output;output+="<p>";output+=text;output+="</p>";cout<<output<<endl;string words[] = {"hello", "world"};ostringstream oss;oss<<"<ul>";for(auto w: words){oss<<" <li>" << w <<"</li>";}oss<<"</ul>";cout<<oss.str()<<endl;return 0;
}
使用 Builder 设计模式
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include <tuple>
#include <sstream>
#include <memory>
using namespace std;struct HtmlElement
{string name, text;vector<HtmlElement> elements;const size_t indent_size =2;HtmlElement(){}HtmlElement(const string& name,const string& text):name(name),text(text){}string str(int intent = 0)const{ostringstream oss;string i(indent_size*intent, ' ');oss << i << "<" << name << ">" << endl;if(text.size() > 0){oss<<string(indent_size*(intent + 1), ' ') << text <<endl;}for(const auto& e:elements) {oss << e.str(intent + 1);}oss<<i << "</" << name << ">" << endl;return oss.str();}};struct HtmlBuilder
{HtmlElement root;HtmlBuilder(string rootname){root.name = rootname;}void add_child(string child_name, string child_text){HtmlElement e{child_name, child_text};root.elements.emplace_back(e);}string str() const {return root.str();}
};int main() {HtmlBuilder builder ("ul");builder.add_child("li", "hello");builder.add_child("li", "world");builder.add_child("li", "hello");cout << builder.str() << endl;return 0;
}
4.注意事项
在实际使用建造者模式时,需要注意以下几点和原则:
1. 分离接口和实现
建造者模式的核心原则是将对象的构建过程与它的表示分离。因此,建造者接口应该只定义创建和组装对象所需的方法,而具体的实现细节应该委托给具体建造者。
2. 确保建造者产品的一致性
指挥者负责协调建造过程,它告诉建造者要创建哪些部分以及如何组装它们。为了确保建造出的产品是一致的,指挥者需要使用相同的建造者接口来调用所有具体建造者。
3. 灵活性和可扩展性
建造者模式的优点之一是它的灵活性和可扩展性。你可以轻松地添加新的具体建造者来创建新的对象类型,而无需修改现有代码。这使得建造者模式非常适合需要创建各种不同对象的场景。
4. 避免创建不必要的具体建造者
虽然建造者模式允许你创建任意数量的具体建造者,但重要的是要避免创建不必要的具体建造者。每个具体建造者都应该代表一种不同的对象类型或变体。
5. 考虑使用不可变对象
对于需要不可变对象的场景,你可以考虑使用不可变对象来表示产品。这可以防止在构建过程之后意外修改对象。
6. 性能考虑
如果性能是一个问题,你需要仔细考虑建造者模式的开销。创建和销毁大量的具体建造者可能会对性能产生影响。
7. 何时使用建造者模式
建造者模式最适合用于创建复杂的对象,这些对象需要经过多个构建步骤才能创建。例如,它可以用在创建文档、GUI 控件或 Web 页面等对象中。
个人觉得如果对象很简单,并且不需要经过多个构建步骤,则建造者模式可能过于复杂。在这种情况下,可以使用更简单的创建模式,例如工厂方法模式或单例模式会更简单。
5.最佳实践
建造者模式的最佳实践包括:
- 使用不可变对象:对于需要不可变对象的场景,使用不可变对象来表示产品可以防止在构建过程之后意外修改对象。
- 使用分层建造者:对于复杂的对象,可以考虑使用分层建造者。分层建造者将构建过程分解成更小的步骤,并允许你创建嵌套的对象结构。
- 使用流式 API:对于需要创建大量相似对象的场景,可以使用流式 API 来简化构建过程。流式 API 允许你将构建步骤链接在一起,并使用链式语法来创建对象。
- 考虑使用生成器函数:在某些情况下,使用生成器函数可以简化建造者模式的实现。生成器函数可以让你一步一步地构建对象,而无需显式地创建和销毁具体建造者。
- 避免创建不必要的具体建造者:每个具体建造者都应该代表一种不同的对象类型或变体。避免创建不必要的具体建造者,因为这会增加代码的复杂性和维护成本。
- 确保建造者产品的一致性:指挥者负责协调建造过程,它告诉建造者要创建哪些部分以及如何组装它们。为了确保建造出的产品是一致的,指挥者需要使用相同的建造者接口来调用所有具体建造者。
6.总结
如果性能是一个问题,你需要仔细考虑建造者模式的开销。创建和销毁大量的具体建造者可能会对性能产生影响。