趣谈设计模式 | 工厂模式(Factory):利用工厂来创建对象
文章目錄
- 案例:外設店鋪
- 簡單工廠
- 工廠方法
- 抽象工廠
- 總結
- 要點
- 三類工廠模式的特點
- 三種工廠模式的適用場景
- 完整代碼與文檔
工廠模式模式是創建型模式中較為常用的一個,它并不是一個模式,而是三種功能相近的設計模式的統稱,它們分別是簡單工廠模式、工廠方法模式、抽象工廠模式,下面我將結合案例來一一講解它們的特點
案例:外設店鋪
假設我們經營著一家外設店鋪,我們主要售賣雷蛇和羅技這兩個型號的鼠標,為了方便用戶購買,我們設計了一個網上購物的平臺,用戶在網上下單后我們會去根據需求來生成鼠標,再經過測試、包裝、注冊信息后,就將合格的產品發送給客戶。于是我們設計的代碼如下
我們設計了一個鼠標類,當有新型號的鼠標發布時,只需要繼承這個類即可9
class Mouse { public:virtual ~Mouse() = default;void showMessage() const{std::cout << "鼠標名:" << _name << "\n" << std::endl;}virtual void test(){std::cout << "正在對鼠標進行測試...." << std::endl;}virtual void packing(){std::cout << "正在對鼠標處理...." << std::endl;}virtual void registerItem(){std::cout << "正在注冊鼠標的商品信息...." << std::endl;} protected:std::string _name; };我們還實現了一個店鋪類,用來處理訂單以及銷售
class PeripheralStore { public:Mouse* orderMouse(const std::string& type) //訂購鼠標{Mouse* mouse; //確定生產的型號if(type == "Logitech_G403"){mouse = new Logitech_G403;}else if(type == "Logitech_G502"){mouse = new Logitech_G502;}else if(type == "Razer_DeathAdder"){mouse = new Razer_DeathAdder;}else if(type == "Razer_Mamba"){mouse = new Razer_Mamba;}mouse->test(); //測試鼠標mouse->packing(); //包裝鼠標mouse->registerItem(); //注冊商品信息return mouse; //發貨} };但是隨著產品的不斷迭代以及我們的銷量,我們售賣的產品時刻都會發生變化,因此我們就會經常來到這里對代碼進行修改。
我們發現,在這段代碼中我們需要改變的只有上面生產鼠標的部分,而下面對鼠標的測試、包裝、注冊都是固定的步驟,是不需要改變的,而我們將這兩者放在一起,違反了我們封閉原則,可能會因為我們生產的操作不當,影響這些固定功能的發揮。那我們要如何做呢?接下來就到工廠模式發揮作用的時候了
簡單工廠
為了將生產與產品處理分割開,我們可以構建出一個鼠標工廠類,將生產鼠標的任務委托給它,它生產完后把鼠標給我們,我們進行處理后即可銷售
鼠標工廠類實現起來很簡單,我們只需要將原來創建鼠標的代碼遷移過去即可
class MouseFactory { public:Mouse* createMouse(const std::string& type){Mouse* mouse;if(type == "Logitech_G403"){mouse = new Logitech_G403;}else if(type == "Logitech_G502"){mouse = new Logitech_G502;}else if(type == "Razer_DeathAdder"){mouse = new Razer_DeathAdder;}else if(type == "Razer_Mamba"){mouse = new Razer_Mamba;}return mouse;} };當有了工廠之后,為了將生產任務轉交給工廠,店鋪代碼修改如下
class PeripheralStore { public:Mouse* orderMouse(const std::string& type) //訂購鼠標{Mouse* mouse;mouse = _factory.createMouse(type); //讓工廠生產鼠標mouse->test(); //測試鼠標mouse->packing(); //包裝鼠標mouse->registerItem(); //注冊商品信息return mouse; //發貨} private:MouseFactory _factory; //工廠對象,讓其來負責鼠標的生產 };此時店鋪的類圖如下
像這樣通過工廠類創建對象,并且根據傳入的參數決定具體子類對象的做法,就是簡單工廠模式
有時候為了避免實例化工廠對象,我們會將創建對象的方法聲明為靜態的,所以簡單工廠模式又被叫做靜態工廠方法模式,但是這種方法也存在缺點,它不能通過繼承來改變創建方法的行為
工廠方法
雖然我們實現了簡單工廠模式,但是我們發現,如果我們新增或者減少鼠標的類型,我們就要去修改口罩工廠中的if-else判斷,這不符合面向對象中的開放-封閉原則,這樣對舊代碼的修改不僅容易出錯,可讀性也不好,我們拓展起來也十分麻煩,如何來優化它呢?
在上面的代碼中,一個工廠類需要負責對所有具體鼠標類的實例化,我們可以進行一個轉變,讓每個工廠只針對一種鼠標類的生產
我們可以將工廠抽象為一個接口,而為每一個鼠標型號都創建一個工廠子類,這些子類分別去實現工廠接口,如下
class IMouseFactory { public:virtual Mouse* createMouse() = 0; };class Logitech_G403_Factory : public IMouseFactory {Mouse* createMouse() override{return new Logitech_G403;} };class Razer_DeathAdder_Factory : public IMouseFactory { public:Mouse* createMouse() override{return new Razer_DeathAdder;} };當我們需要新增一個種類的鼠標的時候,只需要繼承并實現工廠接口即可
這樣一來,我們就依靠面向對象中的多態,將每個工廠進行特化,當我們需要某一種類的鼠標時,只需要創建一個該類型的工廠并調用統一的接口,就可以得到這個類型的鼠標對象
int main() {Logitech_G403_Factory* G403_factory = new Logitech_G403_Factory;Razer_DeathAdder_Factory* DeathAdder_factory = new Razer_DeathAdder_Factory;Mouse* m1 = G403_factory->createMouse();m1->showMessage();Mouse* m2 = DeathAdder_factory->createMouse();m2->showMessage();delete m2;delete m1;delete G403_factory;delete DeathAdder_factory;return 0; }測試結果如下
上面這種做法也就是工廠方法模式,其核心就是每一個產品都對應一個工廠之類,并利用多態特性動態創建對象
工廠方法模式定義了一個創建對象的接口,但由子類來決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類
此時的類圖如下
從類圖中可以看出,與簡單工廠方法相比,工廠方法起到了解耦合的作用,此時的無論是增加還是刪除產品,都不會對工廠接口造成影響,這樣我們程序就更具有彈性,未來想拓展產品時只需要實現接口即可。
但是在實際中,考慮到工廠方法過于復雜的問題,如果在業務邏輯十分簡單的情況下,我們沒必要使用工廠方法模式,這時使用簡單工廠模式更加簡單、方便
抽象工廠
隨著商店越做越大,我們已經不滿足于鼠標這個種類,我們想在商店中引入耳機、手柄、鍵盤等商品。但是如果我們還是使用工廠方法,那就意味著我們還需要創建耳機工廠、手柄工廠、鍵盤工廠…并且根據它們的各種型號再次派生出大量的工廠類。
為了方便舉例,這里假設每個種類的外設我們只賣一種型號 (太多了圖放不下,而且不好舉例)
如果每一個子類都對應一個工廠,那樣不僅代碼會越發繁瑣,代碼的維護也愈發艱難,所以此時就到了抽象工廠模式大展身手的時候了
我們不需要再為每一個產品分配上一個工廠,而是尋找它們之間的關聯,將它們進行分組。對于上面的產品,我們可以發現主要就是雷蛇和羅技兩個品牌,所以我們可以將它們按照品牌進行分組,建立羅技工廠和雷蛇工廠
所以我們根據品牌不同,抽象出一個品牌工廠接口,它提供了生產鼠標、鍵盤、耳機的接口
接著讓雷蛇和羅技分別去實現這個接口
class LogitechFactory : public IPeripheralFactory {Mouse* createMouse() override{return new Razer_DeathAdder;}Earphtones* createEarPhones () override{return new Razer_Mako;}KeyBoard* createKeyBoard() override{return new Razer_Huntsman;} };class RazerFactory : public IPeripheralFactory {Mouse* createMouse() override{return new Logitech_G502;}Earphtones* createEarPhones () override {return new Logitech_G443;}KeyBoard* createKeyBoard() override{return new Logitech_G913;} };下面寫一個測試程序,分別生產一個羅技鼠標和一個雷蛇鍵盤
int main() {IPeripheralFactory* logitech = new LogitechFactory; //羅技工廠IPeripheralFactory* razer = new RazerFactory; //雷蛇工廠Mouse* m1 = logitech->createMouse();m1->showMessage();KeyBoard* k1 = razer->createKeyBoard();k1->showMessage();delete k1;delete m1;delete logitech;delete razer; }通過抽象工廠,我們就可以將產品劃分為幾個大家族
當我們想要增加新種類時(例如加入手柄),就需要到抽象工廠接口以及每一個工廠類中添加一個新的方法。
說到這有人就會疑問,那么這不是不符合開放-封閉規則嗎?確實,抽象工廠模式為了能夠實現這樣的分組,在這方面就做出了犧牲。
如果我們想對鼠標再進行一層細分,即想上面一樣分為具體的型號,那就只需要對鼠標再實現一層的簡單工廠或者工廠方法
并且我們還能發現,其實工廠方法早就以及潛伏在抽象工廠中
從上面的例子中我們可以得出,抽象工廠模式其實就是依據某個特點來將相關(依賴)的產品分組,組內不同的產品就對應同一個工廠類中的不同方法。通過這種分組就能大大減少類的數量
抽象工廠允許客戶使用抽象的接口來創建一組相關的產品,而不需要知道實際產出的產品是什么,這樣一來就使得客戶從具體的產品中被解耦
總結
要點
- 所有的工廠模式都是用來封裝對象的創建
- 所有工廠都通過減少應用程序和具體類之間的依賴來促進松耦合,更加具有彈性
- 所有的工廠都是針對抽象編程而非針對具體類編程
三類工廠模式的特點
簡單工廠模式
- 簡單工廠模式具有唯一的工廠類,通過對傳入的參數做if-else判斷來決定生產的對象
工廠方法模式
- 工廠方法模式提供了一個工廠接口,由多個派生工廠類實現接口,利用繼承以及多態來創建不同的產品對象,避免了大量的條件判斷
- 工廠方法將類的實例化推遲到了子類進行
- 工廠方法中一個工廠對應著一種產品,導致類的數量過多
抽象工廠模式
- 抽象工廠將具有關聯(依賴)的產品進行分組,并且同組中的產品由同一個工廠子類的不同方法創建,大大減少了工廠類的數量
- 抽象工廠通過對象組合的方式維護了一個產品家族
三種工廠模式的適用場景
完整代碼與文檔
如果有需要完整代碼或者markdown文檔的同學可以點擊下面的github鏈接
github
總結
以上是生活随笔為你收集整理的趣谈设计模式 | 工厂模式(Factory):利用工厂来创建对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 趣谈设计模式 | 观察者模式(Obser
- 下一篇: 趣谈设计模式 | 策略模式(Strate