之前提到了简单工厂模式,其核心思想是客户端与接口完全解耦。
而工厂模式是对简单工厂的进一步提炼与总结。
工厂模式的介绍-定义、结构、参考实现、场景
场景
- 实现一个导出数据的应用框架,来让客户选择数据导出的方式,并真正执行数据导出
- 通常这种系统,在导出数据上会与一些与约定的方式,比如导出为csv格式,数据库备份形式,excel格式,XML格式等
从封装的角度来讲,我们希望导出的数据数据的业务功能创建ExportFileApi的具体实例,目前只知道接口,该怎么办?
答:让子类决定实例化某一个类,实现类的实例化的延迟。
工厂模式图大致如下:
话不多说,先上代码,注意看注释:
#include <iostream>
#include <string>
using namespace std;
class ExportFileApi {
public:
virtual bool exportData(string data) = 0;
protected:
ExportFileApi() {}
};
//具体化子类
class ExportTextFile :public ExportFileApi {
public:
bool exportData(string data) {
cout << "正在导出数据" << data << "到csv文件" << endl;
return true;
}
};
//生成数据到数据库
class ExportDB :public ExportFileApi {
public:
bool exportData(string data) {
cout << "正在导出数据" << data << "数据库" << endl;
return true;
}
};
//实现一个ExportOperate,导出数据的业务功能
//这也是接口,具体的工作交给子类实现
class ExportOperate {
public:
bool exportData(string data) {
ExportFileApi* pApi = factoryMethod();
return pApi->exportData(data);
}
protected:
virtual ExportFileApi* factoryMethod() = 0;
};
//具体的实现对象,完成导出工作
class ExportTextFileOperate :public ExportOperate {
//不想让外部来访问
protected:
ExportFileApi* factoryMethod() {
return new ExportTextFile();
}
};
class ExportDBOperate :public ExportOperate {
protected:
ExportFileApi* feactoryMethod() {
return new ExportDB;
}
};
int main() {
ExportOperate* pOperate = new ExportTextFileOperate();
pOperate->exportData("666");
return 0;
}
对于此例的UML工厂模式的UML:
定义
- 功能:工厂主要功能是让父类不知道具体实现的情况下,完成自身的功能调用,而具体的实现延迟到子类来实现。
- 实现:抽象类。工厂中通常父类是一个抽象类,里面包含创建所需对象的抽象方法,这些方法就是工厂方法;也可以实现为一个具体的类,这种情况通常父类中提供获取所需对象的默认实现方法,这样就算没有具体的子类也能够运行。
- 参数和返回值:参数——决定到底选用哪一种具体的实现;返回值——一般是被创建对象的接口对象,也可以是抽象类或者一个具体的类的实例
工厂模式的应用案例与思考-IOC/DI(依赖注入,控制反转)
依赖注入:应用程序依赖容器创建并注入它所需要的外部资源
控制反转:容器控制应用程序,由容器反向的向Application注入程序所需要的
降低A和B的耦合,交由IOC容器去管理。
同时这种思想适用于很多个对象之间的管理,就比如一个老师要管理50甚至上百个学生,需要一个点名册,记录学号等信息,来管理学生。这个点名册就是IOC。
具体看一个例子:
#include <string>
#include <map>
#include <memory>
#include <functional>
#include <iostream>
using namespace std;
template <class T>
class IocContainer {
public:
IocContainer() {}
~IocContainer()
{
}
//注册需要创建对象的构造函数,通过一个唯一的标识,以便于以后查找
template<class Drived>
void RegisterType(string strKey) {
std::function<T* ()>function = [] {return new Drived(); };
RegisterType(strKey, function);
}
//根据唯一的标识查找对应的构造函数
T* Resolve(string strKey) {
if (m_createMap.find(strKey) == m_createMap.end()) {
return nullptr;
}
std::function<T* ()>function = m_createMap[strKey];
return function();
}
//创建智能指针
std::shared_ptr<T>ResolveShared(string strKey) {
T* ptr = Resolve(strKey);
return std::shared_ptr<T>(ptr);
}
private:
void RegisterType(string strKey, std::function<T* ()>creator) {
if (m_createMap.find(strKey) != m_createMap.end()) {
throw std::invalid_argument("已经存在这个key了");
}
m_createMap.emplace(strKey, creator);
}
private:
map<string, std::function<T* ()>>m_createMap;
};
struct ICar {
virtual ~ICar() {}
virtual void test()const = 0;
};
struct Bus:ICar
{
Bus() {}
void test()const { cout << "Bus test" << endl; }
};
struct Track :ICar
{
Track() {}
void test()const { cout << "Track test" << endl; }
};
int main() {
IocContainer<ICar>carIOC;
carIOC.RegisterType<Bus>("bus");
carIOC.RegisterType<Track>("track");
std::shared_ptr<ICar>bus = carIOC.ResolveShared("bus");
bus->test();
std::shared_ptr<ICar>track = carIOC.ResolveShared("track");
track->test();
return 0;
}
工厂模式的本质-依赖倒置-让子类选择实现
- 依赖倒置原则:要依赖抽象,不要依赖于具体类,不能让高层组件依赖于底层组件,而且不管高层组件还是底层组件,都应该依赖于抽象
何时选用工厂模式?
- 如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类去实现
- 如果一个类本身就希望由它的子类来创建所需的对象时候,就应该使用工厂模式。