常用设计模式C++示例1:创建型模式
PS:关于设计模式,推荐参考电子书:设计模式。本博客代码均参考以上电子书,仅供查阅,不便于学习。
1. 单例模式
1.1 概念及实现方法
单例模式保证一个类只存在一个实例,同时提供访问该实例的全局节点,该全局节点仅在首次访问的时候初始化。为了实现单例模式,需要做以下操作:
(1) 构造函数均声明为private,这样外部不能进行单例类初始化;
(2) 提供全局访问节点,private的静态类指针,这样所有类只有一个指针;
(3) 提供静态成员方法getInstance(),该方法在首次访问全局节点时初始化,以后再访问时直接返回已初始化的全局访问节点。
1.2 C++实现
非线程安全实现:
class Singleton { private: Singleton(string s):m_value(s) {}; //private的构造函数 static Singleton* m_instance; //静态成员指针 string m_value; public: Singleton() = delete; //除了getInstance能获取实例,其他构造函数一律禁止 Singleton(Singleton& single) = delete; Singleton operator=(Singleton& single) = delete; static Singleton* getInstance(string s) //访问全局节点函数,获得该节点的指针 { if (m_instance == NULL) m_instance = new Singleton(s); return m_instance; } string value() const { return m_value; } };
Singleton* Singleton::m_instance = nullptr; //静态成员变量只能在类外初始化
因为该类在getInstance()成员函数中没有对m_instance(相当于全局变量)加锁,所以该类是非线程安全的。通过下面代码来验证:
1 void ThreadBar() 2 { 3 //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 4 Singleton* instance = Singleton::getInstance("Bar"); 5 cout << instance->value() << "\n"; 6 } 7 8 void ThreadFoo() 9 { 10 //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 11 Singleton* instance = Singleton::getInstance("Foo"); 12 cout << instance->value() << "\n"; 13 } 14 15 16 int main() 17 { 18 thread th1(ThreadBar); 19 thread th2(ThreadFoo); 20 th1.join(); 21 th2.join(); 22 23 return 0; 24 }
上面的代码通过两个线程去初始化实例,运行时,会出现“Foo"和”Bar"同时打印,说明进行了两次实例化。为了修改为线程安全,在getInstance函数中需要添加锁。修改后的getInstance()如下:
class Singleton { private: static mutex m_mutex; //添加锁 public: static Singleton* getInstance(string s) { if (m_instance == NULL) { lock_guard<mutex> lcok(m_mutex); //上锁 if(m_instance==NULL) m_instance = new Singleton(s); } return m_instance; } }
PS:关于简单工厂模式,工厂模式,抽象工厂模式的区别,可以参考简单工厂模式,工厂模式,抽象工厂模式,也可以参考本博客第四节。
2. 工厂模式
2.1 工厂模式概念及实现要点
工厂模式就是设计一个公共的接口和对应的操作函数。将具体的各个类用抽基类指针或引用表达,具体的操作函数用基类的函数去表达,相当于基类成为具体类的一个接口。所有对具体类的操作都通过基类实现。一个工厂模式有四大要素:
(1) product(所有产品类的抽象类,包含对应的操作函数接口),一般为纯虚类;
(2 )concreteProduct(继承product,实现具体的操作函数),为实际产品类;
(3) creator,声明创建函数,能创建不同的concreteProduct,同时声明操作接口,能调用concreteProduct对应的操作接口,一般为纯虚类;
(4) concreteCreator,创建一个具体concreteProduct的类。
可以看到,以上四要素有两个纯虚基类,用于声明接口。两个字类,concreteCreator用于创建具体的concreteProduct。
对应的一个关系如下图所示:
2.2 C++实现
首先需要实现四个类:
1 //产品类,声明不同的产品接口,纯虚类 2 class product { 3 public: 4 virtual ~product() {}; 5 virtual string operation() const = 0; //操作接口 6 }; 7 8 //具体产品类,实现相同的操作接口 9 class detailedProduct1:public product 10 { 11 public: 12 //实现具体的产品接口 13 string operation()const override { 14 return "operation of detailedProduct 1"; 15 } 16 }; 17 18 class detailedProduct2 :public product 19 { 20 public: 21 //实现具体的产品接口 22 string operation()const override { 23 return "operation of detailedProduct 2"; 24 } 25 }; 26 27 //生产者类,主要声明两种接口: 28 //1. 声明创建函数,根据参数类型,创建不同的product 29 //2. 声明通用的操作接口,根据创建的product调用对应的操作接口 30 class creator { 31 public: 32 virtual ~creator() {}; 33 virtual product* factoryMethod() const = 0; 34 void operation() { 35 product* pro = factoryMethod(); 36 cout << pro->operation() << endl; 37 delete pro; 38 } 39 }; 40 41 //具体的创建者类,创建具体的product 42 class product1Creator:public creator 43 { 44 public: 45 product* factoryMethod() const override { 46 return new detailedProduct1(); 47 } 48 }; 49 50 class product2Creator :public creator 51 { 52 public: 53 product* factoryMethod() const override { 54 return new detailedProduct2(); 55 } 56 };
在使用时,利用基类creator指针指向具体的子类creator创建的对象。然后就能调用子类product的方法接口,整个过程只有一个基类creator指针被创建。可以看到,工厂方法的好处在于:
- 当新的子类出现,只需要实现一个继承自product基类的子类product;再实现一个继承自creator基类的子类creator,对应具体的子类prodcut创建,就能调用相关product的操作函数。整个过程不需要修改原代码,只需要添加新的代码即可。
下面看如何调用工厂模式。
int main() { creator* a1 = new product1Creator(); a1->operation(); creator* a2 = new product2Creator(); a2->operation(); delete a1, a2; return 0; }
3. 抽象工厂模式
3.1 概念及实现要点
抽象工厂模式的特点有:
(1) 存在多个抽象产品,纯虚类,用于声明产品的公有属性和接口;
(2) 每个抽象产品有多个具象产品,实现具体的产品接口;
(3) 一个抽象工厂,生产抽象产品,纯虚类,声明生产产品的接口;
(4) 多个具象工厂,一个具象工厂生产每一种抽象产品的一个具象产品,因此一个抽象产品有多少具象产品,通常就有多少个具象工厂。
对应的关系如下图:
抽象工厂具有如下特点:
(1) 同一工厂生产的产品是不同种类,性质相似的;
(2) 避免了客户端和具体代码耦合;
(3) 单一职责原则,产品生成代码都在具象工厂内,方便修改;
(4) 开闭原则,在引入新的产品时,应用程序/客户端无需修改程序。
3.2 C++代码实现
3.1中已经说明,实现抽象工厂需要四种类:抽象产品类,具象产品类,抽象工厂类,具象工厂类。
1 /*********************************** 2 定义抽象产品类A 3 1. 存在多个抽象产品,比如抽象产品:椅子,桌子,床 4 2. 一个抽象产品可以对应多个具象产品,比如抽象产品椅子,可以对应:有靠背的椅子,躺椅,无靠背椅子 5 但都具有相同的接口 6 ***********************************/ 7 class abstractProductA 8 { 9 public: 10 virtual ~abstractProductA() {}; 11 virtual string functionA() const = 0; 12 }; 13 14 //具象产品A1,实现抽象产品A的初始化的具象函数接口 15 class concreteProductA1:public abstractProductA 16 { 17 public: 18 string functionA() const override 19 { 20 return "Concrete product A1"; 21 } 22 }; 23 24 //具象产品A2,实现抽象产品A的初始化的具象函数接口 25 class concreteProductA2 :public abstractProductA 26 { 27 public: 28 string functionA() const override 29 { 30 return "Concrete product A2"; 31 } 32 }; 33 34 //抽象产品B 35 class abstractProductB { 36 public: 37 virtual ~abstractProductB() {}; 38 virtual string functionB() const = 0; 39 }; 40 41 //具象产品B1 42 class concreteProductB1 :public abstractProductB 43 { 44 public: 45 string functionB() const override { 46 return "Concrete product B1"; 47 } 48 }; 49 50 //具象产品B2 51 class concreteProductB2 :public abstractProductB 52 { 53 public: 54 string functionB() const override { 55 return "Concrete product B2"; 56 } 57 };
然后是工厂类,工厂类的抽象工厂负责定义接口并与客户端代码交互,具象工厂负责具体产品生产:
1 /**************************************** 2 以上声明了产品,还需要对应的工厂去生成产品,工厂也分两类,抽象工厂和具象工厂 3 抽象工厂负责声明统一的创建接口,创建不同的抽象产品,比如此处声明了A,B两类抽象产品 4 *****************************************/ 5 class abstractFactory 6 { 7 public: 8 virtual abstractProductA* createProductA() const = 0; 9 virtual abstractProductB* createProductB() const = 0; 10 }; 11 12 /******************************************** 13 具象工厂,专门生产具体的产品类,通常每个抽象产品类的一个具象产品类组成一组,由一个具象工厂生产 14 比如:此处有抽象产品A,B ,对应具象产品:A1,A2;B1,B2 15 那么有多少种具象产品类,就有多少种具象工厂,每一个具象工厂负责生成所有抽象产品的一个具象产品 16 比如:此处A1,B1为1组,由一个具象工厂生产;A2,B2为一组 17 **********************************************/ 18 class concreteFactory1 :public abstractFactory 19 { 20 public: 21 abstractProductA* createProductA() const override { 22 return new concreteProductA1(); 23 } 24 abstractProductB* createProductB() const override { 25 return new concreteProductB1(); 26 } 27 }; 28 29 class concreteFactory2 :public abstractFactory 30 { 31 public: 32 abstractProductA* createProductA() const override { 33 return new concreteProductA2(); 34 } 35 abstractProductB* createProductB() const override { 36 return new concreteProductB2(); 37 } 38 };
接下来是客户端代码,客户端代码提供的参数输入就是一个抽象工厂类,由于该类是纯虚类,需要通过引用或指针指向具象工厂才能传入实现多态。
1 /*********************************** 2 客户端代码,只与抽象工厂打交道,不跟具象工厂耦合 3 根据传进来的工厂对象的不同,创建不同的产品,实现不同的产品函数 4 ************************************/ 5 void clientCode(const abstractFactory& factory) 6 { 7 abstractProductA* prodcut_a = factory.createProductA(); 8 abstractProductB* product_b = factory.createProductB(); 9 cout << prodcut_a->functionA() << endl; 10 cout << product_b->functionB() << endl; 11 delete prodcut_a; 12 delete product_b; 13 } 14 15 16 17 int main() 18 { 19 concreteFactory1* factory1 = new concreteFactory1(); //创建具象工厂 20 clientCode(*factory1); 21 delete factory1; 22 return 0; 23 24 }
4. 简单工厂,工厂和抽象工厂的区别
以加减乘除算术运算为例,那么此处抽象产品就是算术运算类,命名为arithmeticMethod,具象产品就是addMethod,minusMehod,multiMethod,divideMethod,分别为加减乘除类,那么四种具象类分别实现一个方法getResult,用于实现加减乘除。
4.1 简单工厂
简单工厂一般只有三个类:抽象产品类,具象产品类,工厂类。工厂类中的createProduct方法包含逻辑控制语句,根据不同的swith分支创建不同的具象产品。下面看加减乘除算术运算的一个具体实现方式:
1 //抽象产品类 2 class arithmeticMethod 3 {public: 4 virtual ~arithmeticMethod() {}; 5 virtual double getResult(double a1, double a2) const = 0; 6 }; 7 8 //具象产品类 9 class addMethod:public arithmeticMethod 10 { 11 public: 12 double getResult(double a1, double a2) const override 13 { 14 return a1 + a2; 15 } 16 }; 17 18 //具象产品类 19 class minusMethod :public arithmeticMethod 20 { 21 public: 22 double getResult(double a1, double a2) const override 23 { 24 return a1 - a2; 25 } 26 }; 27 28 //具象产品类 29 class multiMethod :public arithmeticMethod 30 { 31 public: 32 double getResult(double a1, double a2) const override 33 { 34 return a1*a2; 35 } 36 }; 37 38 //具象产品类 39 class divideMethod :public arithmeticMethod 40 { 41 public: 42 double getResult(double a1, double a2) const override 43 { 44 return a1 / a2; //此处不进行类型检测,仅对工厂系列方法进行讲解 45 } 46 }; 47 48 class factory { 49 public: 50 arithmeticMethod* createProduct(char c) 51 { 52 switch (c) { 53 case '+':return new addMethod(); break; 54 case '-':return new minusMethod(); break; 55 case '*':return new multiMethod(); break; 56 case '/':return new divideMethod(); break; 57 } 58 } 59 }; 60 61 //客户端代码 62 void clientCode(factory& f) //抽象产品指针 63 { 64 arithmeticMethod* product = f.createProduct('+'); 65 double res=product->getResult(1.2, 2.4); 66 cout << res << endl; 67 delete product; 68 } 69 70 71 int main() 72 { 73 74 factory* f = new factory(); 75 clientCode(*f); 76 return 0; 77 78 }
简单工厂类所有具象产品的生产均在简单工厂内部,当产品增加时,需要直接修改工厂代码,同时随着产品增加简单工厂的函数内容也会更复杂。
4.2 工厂模式
工厂模式就是把简单工厂进行扩展,分为抽象工厂(只定义生成接口),具象工厂(将简单工厂的switch分支实现为一个具象工厂),这样添加新的类时,只需要添加一个具象工厂类。将4.1的代码修改为工厂模式:
1 /*********************************** 2 加减乘除的具象产品与4.1同理,只需要将简单工厂扩充为一个纯虚类工厂和多个具象工厂 3 ************************************/ 4 5 class factory { 6 public: 7 virtual ~factory() {}; 8 virtual arithmeticMethod* createProduct() const = 0; 9 }; 10 11 class addFactory:public factory 12 { 13 public: 14 arithmeticMethod* createProduct() const override { 15 return new addMethod(); 16 } 17 }; 18 19 class minusFactory :public factory 20 { 21 public: 22 arithmeticMethod* createProduct() const override { 23 return new minusMethod(); 24 } 25 }; 26 27 //乘除具象工厂同理,不再赘述 28 29 30 //客户端代码 31 void clientCode(factory& f) //抽象产品指针 32 { 33 arithmeticMethod* product = f.createProduct(); 34 double res=product->getResult(1.2, 2.4); 35 cout << res << endl; 36 delete product; 37 } 38 39 40 int main() 41 { 42 43 factory* f = new addFactory(); 44 clientCode(*f); 45 return 0; 46 47 }
4.3 抽象工厂
简单工厂和工厂模式均只能生成一种抽象产品,该抽象产品具有多个具象产品。但是当存在多个抽象产品,每个抽象产品又存在多个具象产品时,简单工厂模式和工厂模式就有些捉襟见肘,抽象工厂模式相对于前两种模式的区别就在于:
(1) 抽象工厂模式有多个抽象产品类,每个抽象产品有多个具象产品;
(2) 每个具象工厂不再只生产一种产品类,而是生产每个抽象产品的一个具象产品(同一工厂生产的一组具象产品具有同类共同属性)。
4.1,4.2的例子用工厂模式就能很好解决了,再举一个用于抽象工厂的例子(此例子为设计模式-抽象工厂模式中的例子)。假如现在工厂要生产椅子,桌子,沙发三个抽象产品,同时每个抽象产品又有不同风格,比如欧美风,中国风,非洲风。那么相当于有三个抽象产品,每个抽象产品有三个具象产品,因此声明一个抽象工厂,三个具象工厂,每个具象工厂生产一类风格的产品,比如工厂1只生成中国风的椅子,桌子和沙发(即同一具象工厂内生产的产品来自不同抽象产品类,但是相互间又有一致属性)。代码再次不再详述。
5. 生成器模式
5.1 概念及实现要点
在实际编程时,常常遇到某个类有很多属性,在构造函数初始化时,为了类属性初始化,构造函数的参数列表参数非常多。造成调用麻烦。这个时候,可以将构造函数拆分成几个函数,每个函数初始化一部分参数。举个具体的例子,比如创建一个房屋类,该房屋主要由墙,窗户,门,家具,装饰等属性组成,可以用一系列的生成器,每个生成器调用不同的函数,生成不同类型的房屋。如下图所示。
从上面的例子,也可以看出来一个生成器模式需要哪些基本元素:抽象产品类,抽象生成器类(纯虚类),具象生成器类(多个具象生成器)。除此之外,通常还有一个主管(Director),主管传入一个抽象生成器类,调用生成函数,生成最经典的产品(相当于量产产品)。这样客户端代码,就直接跟Director耦合,不必一次调用众多的属性生成函数。各个部件的结构如下图所示。
5.2 C++实现
按照产品类,抽象生成器类,具象生成器类,主管类,客户端代码五个部分,编写的c++函数如下:
//生成器模式,通过一系列步骤完成初始化,即将构造函数拆分 //产品类 class Product { public: vector<string> m_parts; //所有属性 void listParts() const { for (int i = 0; i < m_parts.size(); i++) { cout << m_parts[i] << " "; } cout << endl; } }; //抽象生成器,定义公有的特性添加接口 class Builder { public: virtual ~Builder() {}; virtual void producePartA() const = 0; virtual void producePartB() const = 0; virtual void producePartC() const = 0; }; //具象的生成器1,生成一类特定产品 class ConcreteBuilder1:public Builder { private: Product* prod; public: ConcreteBuilder1() { reset(); } void reset() { prod = new Product(); //生成产品基本类型 } ~ConcreteBuilder1() { delete prod; } //生成产品其他具体部件 void producePartA() const override { prod->m_parts.push_back("partA1"); } void producePartB() const override { prod->m_parts.push_back("partB1"); } void producePartC() const override { prod->m_parts.push_back("partC1"); } //对于某些产品,其除了公有的部件(生产步骤和生产函数),还有其独有的部件,此种生产函数直接放到具体生成器中 void produceSpecialPart() { prod->m_parts.push_back("specialPart"); } //注:此处返回已经建造完成的产品,此后builder可以再用于建造其他产品 //注意删除使用完成的产品 Product* getProduct() { Product* p = prod; reset(); return p; } }; //具体生成器2,定义的内容和步骤与生成器1相似 //管理者,主要负责通用的产品生产,就调用concreteBuilder的部件生产函数,实现特定流行产品生产 class Director { private: Builder* builder; public: void setBuilder(Builder* b) { builder = b; } void buildPrevalentProduct() { builder->producePartB(); builder->producePartA(); builder->producePartC(); } }; //客户端代码 void clientCode(Director& d) { ConcreteBuilder1* b = new ConcreteBuilder1(); d.setBuilder(b); d.buildPrevalentProduct(); Product* p = b->getProduct(); p->listParts(); delete p; delete b; } int main() { Director* d = new Director(); clientCode(*d); return 0; }
需要注意的是ConcreteBuilder(具象生成器类)的getProduct()函数,该函数返回已经调用Director生产完成的产品指针,同时生成一个新的产品指针,因此用完返回的产品需要人为释放空间。
6. 原型模式
6.1 概念及实现要点
当有一个类对象,希望对其进行复制时,常用的方法是遍历原对象类的成员变量,并将其复制到新的对象中(这通常可以用拷贝构造函数完成)。但是这使得客户端代码直接跟具体的类打交道,造成耦合关系。原型模式就是为了解决类复制问题引入的,其主要的结构和对应需要实现的类如下图:
可以看到,实现一个原型模式,主要有抽象原型,具体原型,原型注册表三大类。抽象原型主要定义clone接口,可以复制一个具体原型类;具体原型实现接口;原型注册表将所有可克隆的原型组织成一个HashTable的形式,直接通过原型注册表的具体原型对象,调用其复制接口获得新的具体原型对象。客户端也只跟原型注册表交互。
6.2 C++实现
//在此给每个具体原型一个编号,声明原型相关信息,客户端通过这个原型编号创建具体的原型 enum Type { PROTOTYPE1 = 0, PROTOTYPE2 }; //抽象原型,最主要的是clone接口 class Prototype { protected: string m_name; int m_val; public: Prototype(string name, int val) :m_name(name), m_val(val) {}; virtual Prototype* clone() const = 0; //克隆函数接口 virtual void showMems() const = 0; }; //具象原型1 class ConcretePrototype1:public Prototype { private: float m_score; //声明其他的一些属性 public: ConcretePrototype1(string name, int val, float score) :Prototype(name, val), m_score(score) {}; //注意克隆方法返回的是一个克隆对象,所以客户端代码要负责删除内存 Prototype* clone() const override { return new ConcretePrototype1(*this); } void showMems() const override { cout << this->m_name << " " << this->m_val << " " << this->m_score << endl; } }; //具象原型2 class ConcretePrototype2 :public Prototype { private: char m_ch; //声明其他的一些属性 public: ConcretePrototype2(string name, int val, char ch) :Prototype(name, val), m_ch(ch) {}; //注意克隆方法返回的是一个克隆对象,所以客户端代码要负责删除内存 Prototype* clone() const override { return new ConcretePrototype2(*this); } void showMems() const override { cout << this->m_name << " " << this->m_val << " " << this->m_ch << endl; } }; //原型工厂,即原型注册器,通常包含所有可克隆的具体原型对象 class PrototypeFactory { private: unordered_map<int, Prototype*> m_prototypes; public: //在创建原型工厂时,生成所有可克隆对象,之后客户端直接调用可克隆对象的clone方法获得新的对象 PrototypeFactory() { m_prototypes[PROTOTYPE1] = new ConcretePrototype1("Prototype1",1,3.5); m_prototypes[PROTOTYPE2] = new ConcretePrototype2("Prototype2", 2, 'h'); } //释放对应的内存 ~PrototypeFactory() { for (auto mem : m_prototypes) delete mem.second; } //调用此函数获得一个具体原型对象,传入type控制输出类型 Prototype* createPrototype(int type) { return m_prototypes[type]->clone(); } }; //客户端代码 void client(PrototypeFactory& factory) { Prototype* type1 = factory.createPrototype(Type::PROTOTYPE1); type1->showMems(); delete type1; } int main() { PrototypeFactory* factory = new PrototypeFactory(); client(*factory); delete factory; return 0; }