前言
创建型模式主要关注对象的创建过程,提供了一种创建对象的最佳方式,并隐藏了创建逻辑的细节。本章介绍创建型模式中的建造者模式和原型模式。
设计模式详解
4. 建造者模式
问题
在某些情况下,一个对象的创建过程会非常复杂,涉及多个步骤,每个步骤都可能有不同的实现方式。如果将所有的创建逻辑都放在一个类中,会导致该类变得庞大且难以维护。同时,如果需要创建不同的变体对象,就需要在类中添加更多的逻辑,会使代码变得混乱。
解决方案
建造者模式提供了一种将一个复杂对象的构建过程与其表示分离的方法。它将对象的构建过程封装在一个独立的“建造者”类中,由该类负责逐步构建对象。这样可以根据需要创建不同的建造者来构建不同的对象变体。通常建造者模式包含以下角色:
- 产品(product):表示正在构建的复杂对象。建造者模式的目标是构建这个产品。
- 抽象建造者(Abstract Builder):定义了构建产品的步骤和方法,但没有具体的实现。不同的具体建造者可以实现不同的构建步骤,从而创建不同的产品变体。
- 具体建造者(Concrete Builder):实现了抽象建造者定义的方法,完成了产品的构建过程。每个具体建造者负责构建特定的产品变体。
- 指导者(Director):负责控制建造的过程。它通过将客户端与具体建造者分离,确保产品构建是按照一定的顺序和规则进行的。
//电脑组装:显示器、鼠标、键盘、主机(主机又包括cpu、显卡、主板等)
//1.找到店铺老板告诉需求
//2.客服安排技术员工组装
//3.产品组装完成
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//抽象产品类
class AbstractProduct {
public:
virtual void SetDisplayer(string displayer) = 0;
virtual void SetMouse(string mouse) = 0;
virtual void SetKeyBoard(string keyBoard) = 0;
virtual void SetHost(string host) = 0;
virtual void Show() = 0;
};
//具体产品类
class Computer : public AbstractProduct {
void SetDisplayer(string displayer) {
m_list.push_back(displayer);
}
void SetMouse(string mouse) {
m_list.push_back(mouse);
}
void SetKeyBoard(string keyBoard) {
m_list.push_back(keyBoard);
}
void SetHost(string host) {
m_list.push_back(host);
}
void Show() {
cout << "电脑配置结果:" << endl;
for (auto v : m_list) {
cout << v << endl;
}
}
vector<string> m_list;
};
//抽象建造者类
class AbstractBuilder {
public:
AbstractBuilder():product(new Computer) {}
//抽象构造过程
virtual void BuildDisplayer(string displayer) = 0;
virtual void BuildMouse(string mouse) = 0;
virtual void BuildKeyBoard(string keyBoard) = 0;
virtual void BuildHost(string host) = 0;
AbstractProduct* Getproduct() {
return product;
}
protected:
AbstractProduct* product;
};
//具体的建造者类--->组装电脑的技术员
class ConcreteBuilder : public AbstractBuilder {
public:
void BuildDisplayer(string displayer) {
product->SetDisplayer(displayer);
}
void BuildMouse(string mouse) {
product->SetMouse(mouse);
}
void BuildKeyBoard(string keyBoard) {
product->SetKeyBoard(keyBoard);
}
void BuildHost(string host) {
product->SetHost(host);
}
};
//指挥者类
class Director {
public:
Director(AbstractBuilder* pBuilder) :pBuilder(pBuilder) {}
AbstractProduct* createProduct(string displayer, string mouse, string keyBoard, string host) {
pBuilder->BuildDisplayer(displayer);
pBuilder->BuildMouse(mouse);
pBuilder->BuildKeyBoard(keyBoard);
pBuilder->BuildHost(host);
return pBuilder->Getproduct();
}
private:
AbstractBuilder* pBuilder;
};
int main() {
AbstractBuilder* pB = new ConcreteBuilder;
Director* pD = new Director(pB);
AbstractProduct* computer = pD->createProduct("华为显示器", "牧马人鼠标", "雷蛇键盘", "外星人");
computer->Show();
return 0;
}
总结
建造者模式与抽象工厂模式的比较
(原文链接:建造者模式与抽象工厂模式的比较)
建造模式和工厂模式极为相似,都是将对象的构建过程和对象的业务处理分离开来,让不同的类型分别承担对象的创建和对象的使用的责任。
-
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
-
与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
-
在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
总的来说,如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车
优缺点
优点:
- 封装性好,构建和表示分离;
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦;
- 控制细节风险,客户端无需详知细节,建造者细化创建过程。
缺点:
- 产品的组成部分必须相同,这限制了其使用范围;
- 如果产品的内部变化复杂,该模式会增加很多的建造者类,导致系统变得很庞大,后期维护成本较大。
5. 原型模式
问题
在某些情况下,需要创建对象的副本,但复制一个对象的成本可能很高,或者希望避免与对象的具体类耦合。例如,当创建对象的过程较为复杂,或者对象包含大量共享的状态时,使用常规的创建方法可能会导致性能下降。
解决方案
原型模式的解决方案是通过一个原型对象,快速地创建出多个一致的对象,并对其进行相关的操作。这允许我们以更高效的方式创建新对象,同时避免了与对象类的直接耦合。(屁话,简单来说就是复制粘贴)
比如文件夹中存放了一个Word文件,你把文件复制了一个副本出来,原件不动,对副本进行修改以达到自己的目的。原型像是一个模板,你可以基于它复制好多对象,而复制出来的副本产生任何变化都不会影响到原型(注意:前提是clone的实现要满足深拷贝)。
原型模式实现步骤
- 提供一个抽象原型类:规定了具体原型对象必须实现的接口。
- 提供多个具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 提供访问类:使用具体原型类中的 clone() 方法来复制新的对象。
#include <iostream>
#include <string>
using namespace std;
class Monkey {
public:
Monkey() {}
virtual ~Monkey() {}
virtual Monkey* Clone() = 0;
virtual void Play() = 0;
};
class SunWuKong : public Monkey {
private:
string m_strName;
public:
SunWuKong(string name) { m_strName = name; }
~SunWuKong() {}
SunWuKong(const SunWuKong& other) : Monkey() {
m_strName = other.m_strName;
}
Monkey* Clone() {
// 调用拷贝构造函数
return new SunWuKong(*this);
}
void Play() {
cout << m_strName << ":金箍棒" << endl;
}
};
int main() {
Monkey* monkey = new SunWuKong("齐天大圣孙悟空");
// 克隆猴子猴孙
Monkey* m1 = monkey->Clone();
Monkey* m2 = m1->Clone();
m1->Play();
delete m1;
m2->Play();
delete monkey;
delete m2;
return 0;
}
原型模式优缺点
优点:
- 如果创建新的对象比较复杂,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
- 简化对象的创建,无需理会创建过程。
- 可以在程序运行时(对象属性发生了变化)获得一份内容相同的实例,他们之间不会相互干扰
缺点:
- 每一个类都必须配备一个克隆方法,对于已有的没有克隆方法的类来说是致命的,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
…
To be continued.