從C++編程入手設計模式
工廠模式
? 我們馬上就要迎來我們的第二個創建型設計模式:工廠方法模式(Factory Method Pattern)。換而言之,我們希望使用一個這樣的接口,使用其他手段而不是直接創建的方式(說的有點奇怪,大致意思是——不是直接new,而是使用父子對象機制,給定一個判斷條件讓我們的工廠類選擇創建具體的子類)
? 這是因為在軟件開發中,直接在代碼中使用 new
關鍵字創建對象會導致代碼與具體類緊密耦合,降低了系統的靈活性和可擴展性。工廠方法模式通過引入工廠接口和具體工廠類,將對象的創建過程封裝起來,使得客戶端代碼與具體產品類解耦,從而提高了系統的可維護性和可擴展性。
? 一個完整的工廠模式中存在四個基本的類。
- 抽象產品(Product):定義產品的接口,是所有具體產品類的父類。
- 具體產品(ConcreteProduct):實現了抽象產品接口的具體類,表示被創建的對象。
- 抽象工廠(Creator):聲明工廠方法
factoryMethod()
,返回抽象產品類型的對象。 - 具體工廠(ConcreteCreator):實現抽象工廠中的工廠方法,返回具體產品的實例。
? 很顯然,抽象的產品和類是一個接口,我們所有的產品都需要滿足這個接口,或者說,是屬于這個產品類的對象,需要被對應的具體的工廠所創建。當然,對于小項目,筆者一般喜歡合并抽象工廠和具體工廠為工廠,這樣的話直接對這工廠類發起對象創建請求即可。
C++實現的一些要點
- 使用抽象類和虛函數:通過定義抽象產品類和抽象工廠類,利用虛函數實現多態性,使得客戶端代碼可以通過基類指針或引用操作具體產品對象。
- 使用智能指針管理對象生命周期:為了避免內存泄漏,建議使用
std::unique_ptr
或std::shared_ptr
管理動態分配的對象。 - 將對象創建邏輯封裝在工廠類中:將具體產品類的實例化過程封裝在具體工廠類中,客戶端代碼只需調用工廠方法獲取產品對象,而無需關心具體的創建細節。
? 下面是一個非常經典的例子,但是不夠好,體現不出來為什么工廠模式存在,但是繪景代碼是如下的:
#include <iostream>
#include <memory>class Shape {
public:virtual void draw() = 0;virtual ~Shape() = default;
};class Circle : public Shape {
public:void draw() override {std::cout << "Drawing a Circle" << std::endl;}
};class Square : public Shape {
public:void draw() override {std::cout << "Drawing a Square" << std::endl;}
};class ShapeFactory {
public:virtual std::unique_ptr<Shape> createShape() = 0;virtual ~ShapeFactory() = default;
};class CircleFactory : public ShapeFactory {
public:std::unique_ptr<Shape> createShape() override {return std::make_unique<Circle>();}
};class SquareFactory : public ShapeFactory {
public:std::unique_ptr<Shape> createShape() override {return std::make_unique<Square>();}
};int main() {std::unique_ptr<ShapeFactory> circleFactory = std::make_unique<CircleFactory>();std::unique_ptr<Shape> circle = circleFactory->createShape(); // 是一個shapecircle->draw(); // 但是是circle,所以調用的就是circle的方法std::unique_ptr<ShapeFactory> squareFactory = std::make_unique<SquareFactory>();std::unique_ptr<Shape> square = squareFactory->createShape();square->draw();return 0;
}
一些你需要注意的事情
- 避免過度使用:在對象創建過程簡單且不會發生變化的情況下,使用工廠方法模式可能會增加系統的復雜性,導致代碼冗余。(換而言之,不要到處用這個東西,除非真需要了(大量相似對象的創建))
- 合理組織類結構:隨著產品種類和工廠類的增加,類的數量也會增加,需要合理組織類結構,避免類爆炸。
- 結合其他設計模式使用:工廠方法模式可以與其他設計模式(如單例模式、抽象工廠模式)結合使用,以滿足更復雜的系統需求。
例子:快餐連鎖店的漢堡制作系統
背景:
您正在為一家快餐連鎖店開發一個漢堡制作系統。不同的快餐品牌(如麥當勞和漢堡王)有各自的漢堡制作方式,包括使用的面包類型、配料和包裝方式。
任務:
- 定義一個抽象基類
Burger
,包含純虛函數grill()
、prepare()
和wrap()
,以及成員變量如name
、bunType
和condiments
。 - 實現具體的漢堡類,如
McDonaldsCheeseBurger
和BurgerKingCheeseBurger
,分別繼承自Burger
,并實現上述方法,輸出相應的制作步驟。 - 創建一個抽象工廠類
BurgerJoint
,包含純虛函數createBurger(const std::string& type)
,用于創建不同類型的漢堡。 - 實現具體的工廠類,如
McDonalds
和BurgerKing
,繼承自BurgerJoint
,根據傳入的類型創建相應的漢堡實例。 - 在主函數中,模擬客戶在不同快餐店點餐的過程,使用工廠類創建漢堡對象,并調用其制作方法。
這些要求你需要做到
- 使用
std::unique_ptr
管理對象生命周期。智能指針是一個好東西,多用用! - 保持代碼的可擴展性,方便將來添加新的快餐品牌或漢堡類型。
實現:modern-cpp-patterns-playground/FactoryBaseMethod/BurgerCreator at main · Charliechen114514/modern-cpp-patterns-playground
class AbstractBurger {
public:virtual void grill() = 0;virtual void prepare() = 0;virtual void wrap() = 0;virtual ~AbstractBurger() = default; /* this is required to the parent calss */
};
首先,咱們起手定義了一個抽象基類 AbstractBurger
,其中包含了 grill()
、prepare()
和 wrap()
三個純虛函數,代表了制作漢堡的三個主要步驟。然后,我為麥當勞和漢堡王分別實現了具體的漢堡類,如 McBurger
、McCheeseBurger
、BurgerKingBurger
和 BurgerKingCheeseBurger
,每個類都根據品牌和漢堡類型的不同,實現了各自的制作流程。
class BurgerProvider {
public:virtual std::unique_ptr<AbstractBurger> create_specifiedBurger(const std::string& specified_type) = 0;virtual ~BurgerProvider() = default;
};
為了創建這些漢堡對象,我定義了一個抽象工廠類 BurgerProvider
,并為每個品牌實現了具體的工廠類 McBurgerProvider
和 BurgerKingProvider
。這些工廠類根據傳入的參數(如 “normal” 或 “cheese”)來決定創建哪種具體的漢堡對象。通過這種方式,客戶端代碼可以通過工廠類來創建所需的漢堡,而無需了解具體的實現細節,從而實現了對象創建的解耦。
習題
背景:
您正在開發一個通知發送系統,支持多種通知方式,如電子郵件(Email)、短信(SMS)和推送通知(Push Notification)。每種通知方式有其特定的發送邏輯和所需的參數。
任務:
- 定義一個抽象基類
Notification
,包含純虛函數send(const std::string& message)
。 - 實現具體的通知類,如
EmailNotification
、SMSNotification
和PushNotification
,分別繼承自Notification
,并實現發送邏輯。 - 創建一個工廠類
NotificationFactory
,包含靜態成員函數createNotification(const std::string& type)
,根據傳入的類型創建相應的通知對象。 - 在主函數中,模擬發送不同類型通知的過程,使用工廠類創建通知對象,并調用其發送方法。
要求:
- 使用
std::unique_ptr
管理對象生命周期。 - 考慮每種通知方式所需的特定參數,并在創建對象時傳入。
- 保持代碼的可擴展性,方便將來添加新的通知方式。
筆者也有自己的實現:modern-cpp-patterns-playground/FactoryBaseMethod/NotificationSystem at main · Charliechen114514/modern-cpp-patterns-playground