跳到內容

C++ 程式設計/程式碼/設計模式/建立型模式

來自 Wikibooks,開放的書籍,為開放的世界

建立型模式

[編輯 | 編輯原始碼]

在軟體工程中,建立型設計模式是處理物件建立機制的設計模式,試圖以適合情況的方式建立物件。物件建立的基本形式會導致設計問題或增加設計的複雜性。建立型設計模式透過某種方式控制這種物件建立來解決這個問題。

在本節中,我們假設讀者對之前介紹過的函式、全域性變數、堆疊與堆、類、指標和靜態成員函式有足夠的瞭解。

正如我們將會看到的那樣,存在多種建立型設計模式,它們都將處理特定的實現任務,這將為程式碼庫建立更高層次的抽象,我們現在將介紹每一種。

建造者

[編輯 | 編輯原始碼]

建造者建立型模式用於將複雜物件的構建與其表示分離,以便相同的構建過程可以建立不同的物件表示。

問題
我們想要構建一個複雜的物件,但是我們不希望有一個複雜的建構函式成員,或者一個需要很多引數的建構函式成員。
解決方案
定義一箇中間物件,其成員函式在物件可用於客戶端之前按部分定義所需的物件。建造者模式允許我們在指定所有建立選項之前延遲物件的構建。
#include <string>
#include <iostream>
#include <memory>
using namespace std;

// "Product"
class Pizza
{
public:
	void setDough(const string& dough)
	{
		m_dough = dough;
	}
	void setSauce(const string& sauce)
	{
		m_sauce = sauce;
	}
	void setTopping(const string& topping)
	{
		m_topping = topping;
	}
	void open() const
	{
		cout << "Pizza with " << m_dough << " dough, " << m_sauce << " sauce and "
			<< m_topping << " topping. Mmm." << endl;
	}
private:
	string m_dough;
	string m_sauce;
	string m_topping;
};

// "Abstract Builder"
class PizzaBuilder
{
public:
	virtual ~PizzaBuilder() {};

	Pizza* getPizza()
	{
		return m_pizza.get();
	}
	void createNewPizzaProduct()
	{
		m_pizza = make_unique<Pizza>();
	}
	virtual void buildDough() = 0;
	virtual void buildSauce() = 0;
	virtual void buildTopping() = 0;
protected:
	unique_ptr<Pizza> m_pizza;
};

//----------------------------------------------------------------

class HawaiianPizzaBuilder : public PizzaBuilder
{
public:
	virtual ~HawaiianPizzaBuilder() {};

	virtual void buildDough()
	{
		m_pizza->setDough("cross");
	}
	virtual void buildSauce()
	{
		m_pizza->setSauce("mild");
	}
	virtual void buildTopping()
	{
		m_pizza->setTopping("ham+pineapple");
	}
};

class SpicyPizzaBuilder : public PizzaBuilder
{
public:
	virtual ~SpicyPizzaBuilder() {};

	virtual void buildDough()
	{
		m_pizza->setDough("pan baked");
	}
	virtual void buildSauce()
	{
		m_pizza->setSauce("hot");
	}
	virtual void buildTopping()
	{
		m_pizza->setTopping("pepperoni+salami");
	}
};

//----------------------------------------------------------------

class Cook
{
public:
	void openPizza()
	{
		m_pizzaBuilder->getPizza()->open();
	}
	void makePizza(PizzaBuilder* pb)
	{
		m_pizzaBuilder = pb;
		m_pizzaBuilder->createNewPizzaProduct();
		m_pizzaBuilder->buildDough();
		m_pizzaBuilder->buildSauce();
		m_pizzaBuilder->buildTopping();
	}
private:
	PizzaBuilder* m_pizzaBuilder;
};

int main()
{
	Cook cook;
	HawaiianPizzaBuilder hawaiianPizzaBuilder;
	SpicyPizzaBuilder    spicyPizzaBuilder;

	cook.makePizza(&hawaiianPizzaBuilder);
	cook.openPizza();

	cook.makePizza(&spicyPizzaBuilder);
	cook.openPizza();
}

你也可以使用最新的 C++17 標準

#include <iostream>
#include <memory>
class Pizza{
public:
    void setDough(const std::string& dough){
        m_dough = dough;
    }
    void setSauce(const std::string& sauce){
        m_sauce = sauce;
    }
    void setTopping(const std::string& topping){
        m_topping = topping;
    }
    void open() const {
        std::cout<<"The Pizza have "<<
        m_dough<<" dough, "<<
        m_sauce<<" sauce, "<<
        m_topping<<" topping."<<
        std::endl;
    }
private:
    std::string m_dough;
    std::string m_sauce;
    std::string m_topping;
};

class PizzaBuilder{
public:
    virtual ~PizzaBuilder() = default;
    void createNewPizza(){
        m_pizza = std::make_unique<Pizza>();
    }
    Pizza* getPizza() {
        return m_pizza.release();
    }
    virtual void buildDough() = 0;
    virtual void buildSauce() = 0;
    virtual void buildTopping() = 0;
protected:
    std::unique_ptr<Pizza> m_pizza;
};

class HawaiianPizzaBuilder:public PizzaBuilder{
public:
    ~HawaiianPizzaBuilder() override = default;
    void buildDough() override {
        m_pizza->setDough("Hawaiian dough");
    }
    void buildSauce() override {
        m_pizza->setSauce("Hawaiian sauce");
    }
    void buildTopping() override {
        m_pizza->setTopping("Hawaiian topping");
    }
};

class SpicyPizzaBuilder:public PizzaBuilder{
public:
    ~SpicyPizzaBuilder() override = default;
    void buildDough() override {
        m_pizza->setDough("Spicy dough");
    }
    void buildSauce() override {
        m_pizza->setSauce("Spicy sauce");
    }
    void buildTopping() override {
        m_pizza->setTopping("Spicy topping");
    }
};

class Cook{
public:
    void openPizza() const {
        m_pizzaBuilder->getPizza()->open();
    }
    void createPizza(PizzaBuilder* pizzaBuilder){
        m_pizzaBuilder = pizzaBuilder;
        m_pizzaBuilder->createNewPizza();
        m_pizzaBuilder->buildDough();
        m_pizzaBuilder->buildSauce();
        m_pizzaBuilder->buildTopping();
    }
private:
    PizzaBuilder* m_pizzaBuilder;
};

int main(){
    Cook cook{};
    HawaiianPizzaBuilder hawaiianPizzaBuilder;
    cook.createPizza(&hawaiianPizzaBuilder);
    cook.openPizza();

    SpicyPizzaBuilder spicyPizzaBuilder;
    cook.createPizza(&spicyPizzaBuilder);
    cook.openPizza();
}
//console output
//The Pizza have Hawaiian dough dough, Hawaiian sauce sauce, Hawaiian topping topping.
//The Pizza have Spicy dough dough, Spicy sauce sauce, Spicy topping topping.

定義:一個從派生類系列中建立一個類例項的實用程式類

抽象工廠

[編輯 | 編輯原始碼]

定義:一個建立多個類系列的例項的實用程式類。它還可以返回特定組的工廠。

工廠設計模式在需要建立許多不同型別的物件的情況下很有用,所有這些物件都派生自一個公共基類。工廠方法定義了一個用於建立物件的方法,子類可以重寫該方法以指定要建立的派生型別。因此,在執行時,工廠方法可以傳遞一個所需物件的描述(例如,從使用者輸入中讀取的字串),並返回指向該物件的新例項的基類指標。當基類使用精心設計的介面時,該模式效果最佳,因此不需要強制轉換返回的物件。

問題
我們想要在執行時根據某些配置或應用程式引數來決定要建立哪個物件。當我們編寫程式碼時,我們不知道應該例項化哪個類。
解決方案
定義一個用於建立物件的介面,但讓子類決定要例項化的類。工廠方法允許類將例項化推遲到子類。

在以下示例中,工廠方法用於在執行時建立筆記型電腦或桌上型電腦物件。

讓我們首先定義 Computer,它是一個抽象基類(介面)及其派生類:LaptopDesktop

 class Computer
 {
 public:
     virtual void Run() = 0;
     virtual void Stop() = 0;
     
     virtual ~Computer() {}; /* without this, you do not call Laptop or Desktop destructor in this example! */
 };
 class Laptop: public Computer
 {
 public:
     void Run() override {mHibernating = false;}; 
     void Stop() override {mHibernating = true;}; 
     virtual ~Laptop() {}; /* because we have virtual functions, we need virtual destructor */
 private:
     bool mHibernating; // Whether or not the machine is hibernating
 };
 class Desktop: public Computer
 {
 public:
     void Run() override {mOn = true;}; 
     void Stop() override {mOn = false;}; 
     virtual ~Desktop() {};
 private:
     bool mOn; // Whether or not the machine has been turned on
 };

實際的 ComputerFactory 類根據物件的真實世界描述返回一個 Computer

 class ComputerFactory
 {
 public:
     static Computer *NewComputer(const std::string &description)
     {
         if(description == "laptop")
             return new Laptop;
         if(description == "desktop")
             return new Desktop;
         return nullptr;
     }
 };

讓我們分析一下這種設計的優勢。首先,存在編譯優勢。如果我們將介面 Computer 移動到包含工廠的單獨標頭檔案中,那麼我們可以將 NewComputer() 函式的實現移動到單獨的實現檔案中。現在,NewComputer() 的實現檔案是唯一需要了解派生類的檔案。因此,如果對 Computer 的任何派生類進行了更改,或者添加了新的 Computer 子型別,則 NewComputer() 的實現檔案是唯一需要重新編譯的檔案。每個使用工廠的人都只關心介面,而介面應該在應用程式的整個生命週期內保持一致。

此外,如果需要新增一個類,並且使用者透過使用者介面請求物件,則可能不需要更改呼叫工廠的任何程式碼來支援附加的計算機型別。使用工廠的程式碼只需將新字串傳遞給工廠,並讓工廠完全處理新型別。

想象一下程式設計一個電子遊戲,你想在將來新增新的敵人型別,每個敵人型別都有不同的 AI 功能,可以以不同的方式更新。透過使用工廠方法,程式的控制器可以呼叫工廠來建立敵人,而不需要依賴或瞭解實際的敵人型別。現在,未來的開發人員可以建立新的敵人,包括新的 AI 控制和新的繪圖成員函式,將其新增到工廠中,並建立一個呼叫工廠的關卡,透過名稱請求敵人。將此方法與XML 關卡描述相結合,開發人員可以建立新的關卡,而無需重新編譯他們的程式。所有這一切都要歸功於將物件的建立與物件的用法分離。

另一個例子

#include <stdexcept>
#include <iostream>
#include <memory>
using namespace std;

class Pizza {
public:
	virtual int getPrice() const = 0;
	virtual ~Pizza() {};  /* without this, no destructor for derived Pizza's will be called. */
};

class HamAndMushroomPizza : public Pizza {
public:
	virtual int getPrice() const { return 850; };
	virtual ~HamAndMushroomPizza() {};
};

class DeluxePizza : public Pizza {
public:
	virtual int getPrice() const { return 1050; };
	virtual ~DeluxePizza() {};
};

class HawaiianPizza : public Pizza {
public:
	virtual int getPrice() const { return 1150; };
	virtual ~HawaiianPizza() {};
};

class PizzaFactory {
public:
	enum PizzaType {
		HamMushroom,
		Deluxe,
		Hawaiian
	};

	static unique_ptr<Pizza> createPizza(PizzaType pizzaType) {
		switch (pizzaType) {
		case HamMushroom: return make_unique<HamAndMushroomPizza>();
		case Deluxe:      return make_unique<DeluxePizza>();
		case Hawaiian:    return make_unique<HawaiianPizza>();
		}
		throw "invalid pizza type.";
	}
};

/*
* Create all available pizzas and print their prices
*/
void pizza_information(PizzaFactory::PizzaType pizzatype)
{
	unique_ptr<Pizza> pizza = PizzaFactory::createPizza(pizzatype);
	cout << "Price of " << pizzatype << " is " << pizza->getPrice() << std::endl;
}

int main()
{
	pizza_information(PizzaFactory::HamMushroom);
	pizza_information(PizzaFactory::Deluxe);
	pizza_information(PizzaFactory::Hawaiian);
}

當要建立的物件型別由原型例項決定時,原型模式用於軟體開發中,該原型例項被克隆以生成新物件。例如,當以標準方式(例如,使用 new 關鍵字)建立新物件的固有成本對於給定應用程式來說過高時,使用這種模式。

實現:宣告一個抽象基類,該類指定一個純虛 clone() 方法。任何需要“多型建構函式”功能的類都從抽象基類派生,並實現 clone() 操作。

這裡,客戶端程式碼首先呼叫工廠方法。此工廠方法根據引數找到具體的類。在這個具體的類上,呼叫 clone() 方法,並由工廠方法返回物件。

  • 這是一個原型方法的示例實現。我們在這裡有所有元件的詳細描述。
    • Record 類是一個純虛類,它有一個純虛方法 clone()
    • CarRecordBikeRecordPersonRecord 作為 Record 類的具體實現。
    • 一個 enum RecordType 作為 Record 類的每個具體實現的一對一對映。
    • RecordFactory 類具有一個 Factory 方法 CreateRecord(…)。此方法需要一個 enum RecordType 作為引數,並根據此引數返回 Record 類的具體實現。
/** Implementation of Prototype Method **/
#include <iostream>
#include <unordered_map>
#include <string>
#include <memory>
using namespace std;

/** Record is the base Prototype */
class Record
{
public:
	virtual ~Record() {}
	virtual void print() = 0;
	virtual unique_ptr<Record> clone() = 0;
};

/** CarRecord is a Concrete Prototype */
class CarRecord : public Record
{
private:
	string m_carName;
	int m_ID;

public:
	CarRecord(string carName, int ID) : m_carName(carName), m_ID(ID)
	{
	}

	void print() override
	{
		cout << "Car Record" << endl
		     << "Name  : "   << m_carName << endl
		     << "Number: "   << m_ID << endl << endl;
	}

	unique_ptr<Record> clone() override
	{
		return make_unique<CarRecord>(*this);
	}
};

/** BikeRecord is the Concrete Prototype */
class BikeRecord : public Record
{
private:
	string m_bikeName;
	int m_ID;

public:
	BikeRecord(string bikeName, int ID) : m_bikeName(bikeName), m_ID(ID)
	{
	}

	void print() override
	{
		cout << "Bike Record" << endl
		     << "Name  : " << m_bikeName << endl
		     << "Number: " << m_ID << endl << endl;
	}

	unique_ptr<Record> clone() override
	{
		return make_unique<BikeRecord>(*this);
	}
};

/** PersonRecord is the Concrete Prototype */
class PersonRecord : public Record
{
private:
	string m_personName;
	int m_age;

public:
	PersonRecord(string personName, int age) : m_personName(personName), m_age(age)
	{
	}

	void print() override
	{
		cout << "Person Record" << endl
			<< "Name : " << m_personName << endl
			<< "Age  : " << m_age << endl << endl;
	}

	unique_ptr<Record> clone() override
	{
		return make_unique<PersonRecord>(*this);
	}
};

/** Opaque record type, avoids exposing concrete implementations */
enum RecordType
{
	CAR,
	BIKE,
	PERSON
};

/** RecordFactory is the client */
class RecordFactory
{
private:
	unordered_map<RecordType, unique_ptr<Record>, hash<int> > m_records;

public:
	RecordFactory()
	{
		m_records[CAR]    = make_unique<CarRecord>("Ferrari", 5050);
		m_records[BIKE]   = make_unique<BikeRecord>("Yamaha", 2525);
		m_records[PERSON] = make_unique<PersonRecord>("Tom", 25);
	}

	unique_ptr<Record> createRecord(RecordType recordType)
	{
		return m_records[recordType]->clone();
	}
};

int main()
{
	RecordFactory recordFactory;

	auto record = recordFactory.createRecord(CAR);
	record->print();

	record = recordFactory.createRecord(BIKE);
	record->print();

	record = recordFactory.createRecord(PERSON);
	record->print();
}

另一個例子

要實現該模式,請宣告一個抽象基類,該類指定一個純虛 clone() 成員函式。任何需要“多型建構函式”功能的類都從抽象基類派生,並實現 clone() 操作。

客戶端不是編寫呼叫 new 運算子以硬編碼類名的程式碼,而是呼叫原型上的 clone() 成員函式,呼叫帶有指定所需特定具體派生類的引數的工廠成員函式,或者透過另一種設計模式提供的某些機制呼叫 clone() 成員函式。

 class CPrototypeMonster
 {
 protected:            
     CString           _name;
 public:
     CPrototypeMonster();
     CPrototypeMonster( const CPrototypeMonster& copy );
     virtual ~CPrototypeMonster();
 
     virtual CPrototypeMonster*    Clone() const=0; // This forces every derived class to provide an override for this function.
     void        Name( CString name );
     CString    Name() const;
 };

 class CGreenMonster : public CPrototypeMonster
 {
 protected: 
     int         _numberOfArms;
     double      _slimeAvailable;
 public:
     CGreenMonster();
     CGreenMonster( const CGreenMonster& copy );
     ~CGreenMonster();
 
     virtual CPrototypeMonster*    Clone() const;
     void  NumberOfArms( int numberOfArms );
     void  SlimeAvailable( double slimeAvailable );
 
     int         NumberOfArms() const;
     double      SlimeAvailable() const;
 };

 class CPurpleMonster : public CPrototypeMonster
 {
 protected:
     int         _intensityOfBadBreath;
     double      _lengthOfWhiplikeAntenna;
 public:
     CPurpleMonster();
     CPurpleMonster( const CPurpleMonster& copy );
     ~CPurpleMonster();
 
     virtual CPrototypeMonster*    Clone() const;
 
     void  IntensityOfBadBreath( int intensityOfBadBreath );
     void  LengthOfWhiplikeAntenna( double lengthOfWhiplikeAntenna );
 
     int       IntensityOfBadBreath() const;
     double    LengthOfWhiplikeAntenna() const;
 };

 class CBellyMonster : public CPrototypeMonster
 {
 protected:
     double      _roomAvailableInBelly;
 public:
     CBellyMonster();
     CBellyMonster( const CBellyMonster& copy );
     ~CBellyMonster();
 
     virtual CPrototypeMonster*    Clone() const;
 
     void       RoomAvailableInBelly( double roomAvailableInBelly );
     double     RoomAvailableInBelly() const;
 };

 CPrototypeMonster* CGreenMonster::Clone() const
 {
     return new CGreenMonster(*this);
 }

 CPrototypeMonster* CPurpleMonster::Clone() const
 {
     return new CPurpleMonster(*this);
 }

 CPrototypeMonster* CBellyMonster::Clone() const
 {
     return new CBellyMonster(*this);
 }

具體怪物類之一的客戶端只需要對 CPrototypeMonster 類物件的引用(指標)才能能夠呼叫 ‘Clone’ 函式並建立該物件的副本。下面的函式演示了這個概念

 void DoSomeStuffWithAMonster( const CPrototypeMonster* originalMonster )
 {
     CPrototypeMonster* newMonster = originalMonster->Clone();
     ASSERT( newMonster );
 
     newMonster->Name("MyOwnMonster");
     // Add code doing all sorts of cool stuff with the monster.
     delete newMonster;
 }

現在 originalMonster 可以作為指向 CGreenMonster、CPurpleMonster 或 CBellyMonster 的指標傳遞。

單例模式確保一個類只有一個例項,並提供一個全域性訪問點以訪問該例項。它以單例集命名,單例集定義為包含一個元素的集合。這在需要一個物件來協調整個系統中的操作時非常有用。

檢查清單

  • 在“單例”類中定義一個私有靜態屬性。
  • 在類中定義一個公共靜態訪問器函式。
  • 在訪問器函式中執行“延遲初始化”(首次使用時建立)。
  • 將所有建構函式定義為受保護或私有。
  • 客戶端只能使用訪問器函式來操作單例。

讓我們看看單例與其他變數型別的區別。

與全域性變數類似,單例存在於任何函式範圍之外。傳統的實現使用單例類的靜態成員函式,該函式將在第一次呼叫時建立一個單例類的單個例項,並永遠返回該例項。以下程式碼示例說明了 C++ 單例類的元素,該類僅儲存一個字串。

 class StringSingleton
 {
 public:
     // Some accessor functions for the class, itself
     std::string GetString() const 
     {return mString;}
     void SetString(const std::string &newStr)
     {mString = newStr;}
 
     // The magic function, which allows access to the class from anywhere
     // To get the value of the instance of the class, call:
     //     StringSingleton::Instance().GetString();
     static StringSingleton &Instance()
     {
         // This line only runs once, thus creating the only instance in existence
         static std::auto_ptr<StringSingleton> instance( new StringSingleton );
         // dereferencing the variable here, saves the caller from having to use 
         // the arrow operator, and removes temptation to try and delete the 
         // returned instance.
         return *instance; // always returns the same instance
     }
 
 private: 
     // We need to make some given functions private to finish the definition of the singleton
     StringSingleton(){} // default constructor available only to members or friends of this class
 
     // Note that the next two functions are not given bodies, thus any attempt 
     // to call them implicitly will return as compiler errors. This prevents 
     // accidental copying of the only instance of the class.
     StringSingleton(const StringSingleton &old); // disallow copy constructor
     const StringSingleton &operator=(const StringSingleton &old); //disallow assignment operator
 
     // Note that although this should be allowed, 
     // some compilers may not implement private destructors
     // This prevents others from deleting our one single instance, which was otherwise created on the heap
     ~StringSingleton(){} 
 private: // private data for an instance of this class
     std::string mString;
 };

單例的變體


Clipboard

待辦
討論 Meyers 單例和其他任何變體。


單例類的應用

單例設計模式的一種常見應用是用於應用程式配置。配置可能需要全域性訪問,並且應用程式配置可能需要未來的擴充套件。在 C 的子集中最接近的替代方案是建立一個全域性的 struct。但這缺乏關於該物件在何處例項化的清晰度,也不能保證該物件的存在。

例如,另一個開發人員在物件的建構函式中使用您的單例的情況。然後,另一個開發人員決定在全域性範圍內建立第二個類的例項。如果您只是使用全域性變數,那麼連結順序就會很重要。由於您的全域性變數將被訪問,可能在 main 開始執行之前,因此沒有定義是全域性變數先初始化還是第二個類的建構函式先呼叫。這種行為可能會隨著對程式碼其他部分的微小修改而發生變化,從而改變全域性程式碼執行的順序。這樣的錯誤很難除錯。但是,使用單例,第一次訪問物件時,物件也會被建立。現在您有一個始終存在於被使用狀態下的物件,並且如果從未被使用,它就永遠不會存在。

此類的另一個常見用途是更新舊程式碼以在新的架構中工作。由於開發人員可能廣泛地使用了全域性變數,因此將它們移到一個單獨的類中並將其設為單例,可以作為將程式與更強大的面向物件結構保持一致的中間步驟。

另一個例子

#include <iostream>
using namespace std;

/* Place holder for thread synchronization mutex */
class Mutex
{   /* placeholder for code to create, use, and free a mutex */
};

/* Place holder for thread synchronization lock */
class Lock
{   public:
        Lock(Mutex& m) : mutex(m) { /* placeholder code to acquire the mutex */ }
        ~Lock() { /* placeholder code to release the mutex */ }
    private:
        Mutex & mutex;
};

class Singleton
{   public:
        static Singleton* GetInstance();
        int a;
        ~Singleton() { cout << "In Destructor" << endl; }

    private:
        Singleton(int _a) : a(_a) { cout << "In Constructor" << endl; }

        static Mutex mutex;

        // Not defined, to prevent copying
        Singleton(const Singleton& );
        Singleton& operator =(const Singleton& other);
};

Mutex Singleton::mutex;

Singleton* Singleton::GetInstance()
{
    Lock lock(mutex);

    cout << "Get Instance" << endl;

    // Initialized during first access
    static Singleton inst(1);

    return &inst;
}

int main()
{
    Singleton* singleton = Singleton::GetInstance();
    cout << "The value of the singleton: " << singleton->a << endl;
    return 0;
}

注意
在上面的示例中,第一次呼叫 Singleton::GetInstance 將初始化單例例項。此示例僅用於說明目的;對於除簡單示例程式之外的任何內容,此程式碼都包含錯誤。

華夏公益教科書