工厂模式
顾名思义,工厂是制造产品的。在c++中,工厂模式也是制造产品的,只是这个产品是对象。
c++中创建对象的方式有很多,如直接声明的方式创建栈上对象、使用new创建堆上对象等。而工厂模式主要用于创建堆上对象,属于创建型模式。
为什么不使用new而要绕道使用一个工厂呢?原因如下:
- 直接使用new会导致模块间的紧耦合,并产生编译时依赖,使用工厂方法可以避免编译时依赖,符合依赖倒置原则
- 如果直接使用new,当具体的对象构造函数等发生更改时,使用该对象的所有类都要修改,使用工厂时只需要修改发生变化的类和工厂,客户不作改动
- 直接使用new会要求客户端必须清楚地知道需要使用哪一个子类,使用工厂模式则只需要知道类别即可
- 当增加一个新类时,客户只需要知道增加的工厂类型,不必关心具体的新类,便于新增和扩展,符合开放封闭原则
工厂模式又细分为简单工厂、工厂方法和抽象工厂三种方式。
简单工厂
由一个工厂类产生多个具体的对象,需要在工厂类创建对象时根据条件判断具体要创建哪一个类。
优点:
- 隐藏了对象创建的细节,将产品的实例化推迟到子类中实现
- 实现简单
缺点:
- 每增加新的类时,需要修改工厂类,添加一个新的判断分支
以交易为例,可分为股票(stock)交易和期货(future)交易。使用工厂模式创建具体交易对象时的示例代码如下:
// 代码未测试,供参考(下同)
#include <iostream>
#include <memory>
// 简单工厂
// 类别枚举
enum class InvestmentType { STOCK, FUTURE};
// 抽象基类
class Investment
{
public:
virtual void transaction() = 0;
}
// 实际项目中各个子类可能位于单独的h文件
class StockInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Stock..." << std::endl;
// dosomething
}
}
class FutureInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Future..." << std::endl;
// dosomething
}
}
// 工厂类,使用智能指针,客户端无需关心资源释放问题。客户端可根据需要转换为std::shared_ptr
// 创建对象的工作在此完成,具体创建的对象由传入的参数指定
// 新增对象类型时,需要增加条件判断语句来创建新对象
class InvestmentFactory
{
public:
static std::unique_ptr<Investment> CreateInvestment(InvestmentType type)
{
std::unique_ptr<Investment> p;
if (type == InvestmentType::STOCK)
{
p.reset(new StockInvest);
return p;
}
else
{
p.reset(new FutureInvest);
return p;
}
}
}
int main()
{
// 创建股票交易对象
auto investObj = InvestmentFactory::CreateInvestment(InvestmentType::STOCK);
investObj->transaction();
return 0;
}
示例中使用了c11中的智能指针std::unique_ptr管理对象资源,客户端不必关心资源释放问题。
关于为什么选择unique_ptr,请参考 c++中智能指针使用小结
工厂方法模式
针对简单工厂每新增一类交易都要修改工厂类增加判断分支的问题,工厂方法会为每类交易维护自己的工厂类,由纯虚工厂类提供对外服务。
上述代码可以修改为:
#include <iostream>
#include <memory>
// 工厂方法
// 保持不变
class Investment
{
public:
virtual void transaction() = 0;
}
// 更改为纯虚基类
class InvestmentFactory
{
public:
virtual std::unique_ptr<Investment> CreateInvestment() = 0;
}
// 实际项目中各个子类可能位于单独的h文件,保持不变
class StockInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Stock..." << std::endl;
// dosomething
}
}
// 新增股票交易工厂类
class StockInvestFactory: public InvestmentFactory
{
public:
virtual std::unique_ptr<Investment> CreateInvestment()
{
std::unique_ptr<Investment> p;
p.reset(new StockInvest);
return p;
}
}
// 保持不变
class FutureInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Future..." << std::endl;
// dosomething
}
}
// 新增期货交易工厂类
class FutureInvestFactory: public InvestmentFactory
{
public:
virtual std::unique_ptr<Investment> CreateInvestment()
{
std::unique_ptr<Investment> p;
p.reset(new FutureInvest);
return p;
}
}
int main()
{
// 创建股票交易对象
InvestmentFactory* factory = new StockInvestFactory(); // 实际项目中,可通过配置文件或外部传入
auto investObj = factory->CreateInvestment();
investObj->transaction();
return 0;
}
由此可见,当新增类别时,只管增加相关的工厂类,工厂基类不需要更改,且客户端传入具体的工厂类后,创建并使用对象即可。
注意,由于每个类别都要有工厂类,当类别较多时,就会有较多的工厂类。
抽象工厂
上面两种工厂模式有一个共同点:子类都是继承自同一基类。如果新增的不是该基类派生的子类,就需要抽象工厂了。
交易中,除了投资,还需要计算收益,而股票和期货的计算方式不同。所以就有了下面的故事:
// 代码中未考虑资源管理,请参考上例中的unique_ptr
include <iostream>
#include <memory>
// 抽象工厂
// 保持不变
class Investment
{
public:
virtual void transaction() = 0;
}
// 不变
class StockInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Stock..." << std::endl;
// dosomething
}
}
// 不变
class FutureInvest : public Investment
{
public:
virtual void transaction() override
{
std::cout << "Begin Future..." << std::endl;
// dosomething
}
}
// 计算收益的基类
class CalcProfit
{
public:
virtual void profit() = 0;
}
// 新增
class StockProfit : public CalcProfit
{
public:
virtual void profit() override
{
std::cout << "Begin Stock profit..." << std::endl;
// dosomething
}
}
// 新增
class FutureProfit : public CalcProfit
{
public:
virtual void profit() override
{
std::cout << "Begin Future profit..." << std::endl;
// dosomething
}
}
// 抽象工厂
class Factory
{
public:
virtual Investment* CreateInvestment() = 0;
virtual CalcProfit* CreateCalcProfit() = 0;
}
class StockFactory : public Factory
{
public:
virtual Investment* CreateInvestment()
{
return new StockInvest;
}
virtual CalcProfit* CreateCalcProfit()
{
return new StockProfit;
}
}
class FutureFactory : public Factory
{
public:
virtual Investment* CreateInvestment()
{
return new FutureInvest;
}
virtual CalcProfit* CreateCalcProfit()
{
return new FutureProfit;
}
}
int main()
{
// 创建股票交易对象
Factory* factory = new StockFactory(); // 实际项目中,可通过配置文件或外部传入
auto investObj = factory->CreateInvestment();
investObj->transaction();
auto profitObj = factory->CreateCalcProfit();
profitObj->profit();
return 0;
}
可见,抽象工厂用于创建一系列相关或相互依赖的对象时特别方便。
比如股票交易中既有投资,双要计算收益,期货交易也是这样,所以它们就形成了两个系列,每个系列一个工厂,并且在需要添加新的操作时,直接在工厂类中增加即可。
现实中这样的例子也有很多,如数据库的操作需要连接、读取、写入等一系列操作,它们是一个抽象工厂。而不同的数据库(mysql/oracle/sqlserver等)操作方式不同,它们之间属于不同的系列。
一旦设定好了抽象工厂基类,就可以增加任意多个系列的子类。
总结
工厂模式的三种方法可以根据实际业务场景选择。一般很难在编码之初就深刻意识到设计模式的用武之地,但随着编码的进行,应用场景就会浮出水面。
工厂模式的实现也并非一成不变,相反,在具体的场景下进行适当的变换效果会更佳。
工厂模式使用普遍,但不要生搬硬套。只有在深入理解的基础上运用,才能游刃有余。
参考资料
《设计模式》