天天看點

設計模式之工廠模式(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等)操作方式不同,它們之間屬于不同的系列。

一旦設定好了抽象工廠基類,就可以增加任意多個系列的子類。

總結

工廠模式的三種方法可以根據實際業務場景選擇。一般很難在編碼之初就深刻意識到設計模式的用武之地,但随着編碼的進行,應用場景就會浮出水面。

工廠模式的實作也并非一成不變,相反,在具體的場景下進行适當的變換效果會更佳。

工廠模式使用普遍,但不要生搬硬套。隻有在深入了解的基礎上運用,才能遊刃有餘。

參考資料

《設計模式》

繼續閱讀