【C++设计模式】工厂方法设计模式:深入解析从基础到进阶
1. 引言
在软件开发的世界里,设计模式如同巧妙的建筑蓝图,为解决常见问题提供了行之有效的方案。工厂方法模式作为一种广受欢迎的创建型设计模式,以其独特的优势在众多项目中得到广泛应用。它不仅能够为对象的创建提供通用且灵活的方式,还能有效隐藏实现细节,提升代码的可维护性和可扩展性。本文将全方位深入探讨工厂方法模式,从基础定义、实现过程,到进阶优化和功能扩展,带领读者全面掌握这一重要的设计模式。
2. 工厂方法模式的基础定义与目标
2.1 定义
工厂方法模式提供了一种通用的方式来创建对象实例,它能够很好地隐藏实现细节,特别是对于派生类的实现细节。这种模式属于创建型设计模式,核心在于将对象的创建和使用分离,通过一个工厂函数,根据输入参数来返回正确的对象实例。
2.2 目标
在实际开发中,直接在代码中显式地创建具体对象会增加代码的耦合度,不利于后续的扩展和维护。例如在游戏开发场景下,若有一个 GameObject
类型的对象,直接使用 new ObjectOne
这样的方式创建对象,会使代码变得复杂且难以管理。而工厂方法模式通过工厂函数,避免了在代码中直接决定创建哪种对象,从而提高了代码的可维护性和灵活性。
3. 基础实现工厂方法模式
3.1 利用继承和多态
工厂方法模式的关键在于利用继承和基于继承的多态性。首先,我们创建一个接口类 IGameObject
:
class IGameObject {
public:
virtual ~IGameObject() = default;
virtual void update() = 0;
virtual void render() = 0;
};
这里的 update
和 render
是纯虚函数,任何从 IGameObject
派生的类都必须实现这些函数。
接着,创建具体的派生类,如 Plane
和 Boat
:
class Plane : public IGameObject {
public:
Plane() {}
void update() override {}
void render() override {}
};
class Boat : public IGameObject {
public:
Boat() {}
void update() override {}
void render() override {}
};
3.2 创建工厂函数
创建一个工厂函数 makeGameObjectFactory
,用于根据输入参数创建相应的对象:
#include <string>
#include <memory>
std::unique_ptr<IGameObject> makeGameObjectFactory(const std::string& type) {
if (type == "plane") {
return std::make_unique<Plane>();
} else if (type == "boat") {
return std::make_unique<Boat>();
}
return nullptr;
}
在 main
函数中,可以这样使用工厂函数:
int main() {
std::unique_ptr<IGameObject> myObject = makeGameObjectFactory("plane");
if (myObject) {
myObject->update();
myObject->render();
}
return 0;
}
4. 基础优化工厂方法模式
4.1 避免使用字符串作为参数
使用字符串作为工厂函数的参数存在一些问题,例如大小写敏感、容易输入错误等。为了避免这些问题,我们可以使用 enum class
来定义对象类型:
enum class ObjectType {
Plane,
Boat
};
std::unique_ptr<IGameObject> makeGameObjectFactory(ObjectType type) {
switch (type) {
case ObjectType::Plane:
return std::make_unique<Plane>();
case ObjectType::Boat:
return std::make_unique<Boat>();
default:
return nullptr;
}
}
在 main
函数中,调用方式如下:
int main() {
std::unique_ptr<IGameObject> myObject = makeGameObjectFactory(ObjectType::Plane);
if (myObject) {
myObject->update();
myObject->render();
}
return 0;
}
4.2 使用智能指针
为了避免手动管理内存,我们可以使用智能指针,如 std::unique_ptr
或 std::shared_ptr
。在上述代码中,我们已经使用了 std::unique_ptr
,它可以在对象不再使用时自动释放内存,避免了内存泄漏的问题。
5. 工厂方法模式的进阶扩展
5.1 单例包装工厂
5.1.1 动机与设计思路
单例模式确保一个类只有一个实例,并提供一个全局访问点。将工厂方法包装成单例类,我们可以实现唯一的对象创建点,这对于某些需要严格控制对象创建的场景非常有用。同时,单例包装还可以为工厂方法添加更多的抽象层次,从而实现一些额外的功能,例如对象计数。
5.1.2 代码实现
创建一个名为 FactoryGameObject
的类,并将之前的工厂方法 makeGameObjectFactory
移入该类中。为了实现单例模式,将构造函数、析构函数和拷贝构造函数设为私有,同时将工厂方法设为静态成员函数:
#include <memory>
#include <iostream>
// 前向声明 IGameObject
class IGameObject;
class FactoryGameObject {
private:
FactoryGameObject() = default;
~FactoryGameObject() = default;
FactoryGameObject(const FactoryGameObject&) = delete;
FactoryGameObject& operator=(const FactoryGameObject&) = delete;
static std::shared_ptr<IGameObject> createObject(const std::string& type);
public:
static std::shared_ptr<IGameObject> getInstance(const std::string& type) {
return createObject(type);
}
};
在上述代码中,将 createObject
方法设为私有,通过公共的静态方法 getInstance
来调用它,以确保外部代码只能通过单例接口访问工厂方法。
5.2 对象计数功能实现
5.2.1 静态成员变量与初始化
为了实现对象计数功能,在 FactoryGameObject
类中添加静态成员变量来记录每种对象的创建数量:
class FactoryGameObject {
private:
// ... 之前的代码 ...
static int s_planeCount;
static int s_boatCount;
static std::shared_ptr<IGameObject> createObject(const std::string& type) {
if (type == "plane") {
s_planeCount++;
return std::make_shared<Plane>();
} else if (type == "boat") {
s_boatCount++;
return std::make_shared<Boat>();
}
return nullptr;
}
public:
// ... 之前的代码 ...
static void printCounts() {
std::cout << "Planes created: " << s_planeCount << std::endl;
std::cout << "Boats created: " << s_boatCount << std::endl;
}
};
// 静态成员变量初始化
int FactoryGameObject::s_planeCount = 0;
int FactoryGameObject::s_boatCount = 0;
在上述代码中,在 createObject
方法中对相应的计数器进行递增操作,并提供了一个 printCounts
方法来输出计数结果。
5.2.2 测试代码
int main() {
auto plane = FactoryGameObject::getInstance("plane");
auto boat1 = FactoryGameObject::getInstance("boat");
auto boat2 = FactoryGameObject::getInstance("boat");
FactoryGameObject::printCounts();
return 0;
}
运行上述代码,我们可以看到输出结果显示创建了 1 个飞机对象和 2 个船对象。
5.3 统计活跃对象数量
5.3.1 问题分析
虽然我们已经实现了对象创建数量的统计,但对于活跃对象(即尚未被销毁的对象)的数量统计,由于使用了 std::shared_ptr
,我们无法直接得知对象是否已经被销毁。
5.3.2 解决方案
- 适配器模式:创建一个适配器类来包装
std::shared_ptr
,并在构造函数和析构函数中进行计数操作:
template <typename T>
class MicsCountedSmartPointer {
private:
std::shared_ptr<T> m_ptr;
static int s_count;
public:
MicsCountedSmartPointer(const std::shared_ptr<T>& ptr) : m_ptr(ptr) {
s_count++;
}
~MicsCountedSmartPointer() {
s_count--;
}
static int getCount() {
return s_count;
}
};
template <typename T>
int MicsCountedSmartPointer<T>::s_count = 0;
- 弱指针方法:使用
std::weak_ptr
。维护一个std::weak_ptr
列表,定期遍历该列表,检查哪些对象已经被销毁:
#include <vector>
#include <memory>
class FactoryGameObject {
private:
// ... 之前的代码 ...
static std::vector<std::weak_ptr<IGameObject>> s_objectList;
static std::shared_ptr<IGameObject> createObject(const std::string& type) {
std::shared_ptr<IGameObject> obj;
if (type == "plane") {
s_planeCount++;
obj = std::make_shared<Plane>();
} else if (type == "boat") {
s_boatCount++;
obj = std::make_shared<Boat>();
}
if (obj) {
s_objectList.push_back(obj);
}
return obj;
}
public:
// ... 之前的代码 ...
static int getActiveObjectCount() {
int activeCount = 0;
for (const auto& weakPtr : s_objectList) {
if (!weakPtr.expired()) {
activeCount++;
}
}
return activeCount;
}
};
std::vector<std::weak_ptr<IGameObject>> FactoryGameObject::s_objectList;
6. 工厂方法模式的优缺点
6.1 优点
- 单一职责:工厂函数的职责明确,就是根据输入参数创建相应的对象,使得代码的逻辑更加清晰。
- 灵活性:可以根据不同的需求轻松扩展工厂函数,支持更多的对象类型。
- 可维护性:将对象的创建和使用分离,降低了代码的耦合度,便于后续的维护和修改。
6.2 缺点
- 代码更新:当需要添加新的对象类型时,需要同时更新工厂函数和相关的类定义,这可能会增加代码的维护成本。
- 多个工厂:对于复杂的系统,可能需要多个工厂函数来处理不同的对象层次结构,这会增加代码的复杂度。
7. 总结
工厂方法模式作为一种强大且实用的设计模式,为对象的创建提供了一种优雅而灵活的解决方案。从基础的定义和实现,到进阶的优化和功能扩展,我们逐步深入了解了该模式的各个方面。通过合理运用继承、多态、单例模式以及智能指针等技术,我们可以充分发挥工厂方法模式的优势,同时避免其潜在的缺点。在实际开发中,我们应根据具体的需求和场景,灵活运用工厂方法模式,并结合其他设计模式,构建出高效、可维护和可扩展的软件系统。