跳到内容

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 将初始化单例实例。此示例仅用于说明目的;对于除简单示例程序之外的任何内容,此代码都包含错误。

华夏公益教科书