设计模式之工厂模式(c++)

工厂模式

顾名思义,工厂是制造产品的。在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等)操作方式不同,它们之间属于不同的系列。

一旦设定好了抽象工厂基类,就可以增加任意多个系列的子类。

总结

工厂模式的三种方法可以根据实际业务场景选择。一般很难在编码之初就深刻意识到设计模式的用武之地,但随着编码的进行,应用场景就会浮出水面。

工厂模式的实现也并非一成不变,相反,在具体的场景下进行适当的变换效果会更佳。

工厂模式使用普遍,但不要生搬硬套。只有在深入理解的基础上运用,才能游刃有余。

参考资料

《设计模式》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值