设计模式的 6 大设计原则
- 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
- 开放封闭原则:软件实体可以扩展,但是不可修改。即面对需求,对程序的改动可以通过增加代码来完成,但是不能改动现有的代码。
- 里氏代换原则:一个软件实体如果使用的是一个基类,那么一定适用于其派生类。即在软件中,把基类替换成派生类,程序的行为没有变化。
- 依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要对实现编程。
- 迪米特原则:如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。
- 接口隔离原则:每个接口中不存在派生类用不到却必须实现的方法,不然就要将接口拆分,使用多个隔离的接口。
单一职责原则是指一个类应该是一组相关性很高的函数和数据的封装,这是为了提高程序的内聚性;而其他五个原则是通过抽象来实现的,目的是为了降低程序的耦合性以及提高可扩展性。
设计模式分为三类:
- 创造型模式:单例模式、工厂模式、建造者模式、原型模式;
- 结构型模式:适配器模式、桥接模式、外观模式、组合模式、装饰模式、享元模式、代理模式;
- 行为型模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式。
单例模式
单例模式(Singleton Pattern,也称为单件模式),使用最广泛的设计模式之一。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
应用场景:
- 设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;
- 数据池,用来缓存数据的数据结构,需要在一处写、多处读取或者多处写、多处读取;
- 表示文件系统的类,一个操作系统一定是只有一个文件系统,因此文件系统的类的实例有且仅有一个;
- 打印机打印程序实例,一台计算机可以连接好几台打印机,但是计算机上的打印程序只有一个,就可以通过单例模式来避免两个打印作业同时输出到打印机。
定义一个单例类:
私有化它的构造函数,以防止外界创建单例类的对象;
使用类的私有静态指针变量指向类的唯一实例;
使用一个公有的静态方法获取该实例。
基础要点:
全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private);
线程安全;
禁止赋值和拷贝;
用户通过接口获取实例:使用 static 类成员函数。
实现单例的几种方式:
单例模式可以通过全局或者静态变量的形式实现,这样比较简单,但是这样会影响封装性,难以保证别的代码不会对全局变量造成影响。
默认的构造函数、拷贝构造函数、赋值构造函数声明为私有的,这样禁止在类的外部创建该对象。
全局访问点也要定义成 静态类型的成员函数,没有参数,返回该类的指针类型。因为使用实例化对象的时候是通过类直接调用该函数,并不是先创建一个该类的对象,通过对象调用。
(1)有缺陷的懒汉式
懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象(即延迟初始化),也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。
不安全的实现方式:
class Singleton{
private:
static Singleton * instance;
Singleton(){}
Singleton(const Singleton& tmp){}
Singleton& operator=(const Singleton& tmp){}
public:
static Singleton* getInstance(){
if(instance == NULL){
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = NULL;
原因:考虑当两个线程同时调用 getInstance 方法,并且同时检测到 instance 是 NULL,两个线程会同时实例化对象,不符合单例模式的要求。
存在的问题:
- 线程安全的问题,当多线程获取单例时有可能引发竞态条件。解决办法:加锁,双检测锁模式(通过加锁前检测是否已经初始化,避免了每次获取实例时都要首先获取锁资源)。线程安全问题仅出现在第一次初始化(new)过程中。加锁存在的问题:每次判断实例对象是否为空,都要被锁定,如果是多线程的话,就会造成大量线程阻塞。
class Singleton{
private:
static pthread_mutex_t mutex;
static Singleton * instance;
Singleton(){
pthread_mutex_init(&mutex, NULL);
}
Singleton(const Singleton& tmp){}
Singleton& operator=(const Singleton& tmp){}
public:
static Singleton* getInstance(){
pthread_mutex_lock(&mutex);
if(instance == NULL){
instance = new Singleton();
}
pthread_mutex_unlock(&mutex);
return instance;
}
};
Singleton* Singleton::instance = NULL;
pthread_mutex_t Singleton::mutex;
- 内存泄漏,注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用,因此会导致内存泄漏。解决办法: 使用智能指针; 使用静态的嵌套类对象。
最推荐的懒汉式单例(magic static )——局部静态变量:
通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
不需要使用共享指针,代码简洁;
注意在使用的时候需要声明单例的引用 Single& 才能获取对象。
class Singleton{
private:
static pthread_mutex_t mutex;
Singleton(){
pthread_mutex_init(&mutex, NULL);
}
Singleton(const Singleton& temp){}
Singleton& operator=(const Singleton& temp){}
public:
static Singleton* getInstance(){
static Singleton instance;
return &instance;
}
};
pthread_mutex_t Singleton::mutex;
(2)饿汉版(Eager Singleton)
指单例实例在程序运行时被立即执行初始化。
class Singleton{
private:
static Singleton* instance;
Singleton(const Singleton& temp){}
Singleton& operator=(const Singleton& temp){}
protected:
Singleton(){}
public:
static Singleton* getInstance(){
return instance;
}
};
Singleton* Singleton::instance = new Singleton();
由于在main函数之前初始化,所以没有线程安全的问题。但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。
总结:
Eager Singleton 虽然是线程安全的,但存在潜在问题;
Lazy Singleton通常需要加锁来保证线程安全,但局部静态变量版本在C++11后是线程安全的;推荐使用局部静态变量版本(Meyers Singleton)。
工厂模式
工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式。
简单工厂模式:主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
工厂方法模式:修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。
抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
简单工厂模式
主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
应用场景:适用于针对不同情况创建不同类时,只需传入工厂类的参数即可,无需了解具体实现方法。例如:计算器中对于同样的输入,执行不同的操作:加、减、乘、除。
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
// Here is the Factory class
class Factory
{
public:
static Operation *CreateProduct(char op)
{
switch (op)
{
case '+':
return new Add_Operation();
case '-':
return new Sub_Operation();
case '*':
return new Mul_Operation();
case '/':
return new Div_Operation();
default:
return new Add_Operation();
}
}
};
int main()
{
int a, b;
cin >> a >> b;
Operation *p = Factory::CreateProduct('+');
p->var1 = a;
p->var2 = b;
cout << p->GetResult() << endl;
p = Factory::CreateProduct('*');
p->var1 = a;
p->var2 = b;
cout << p->GetResult() << endl;
return 0;
}
工厂方法模式
修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端去实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。
应用场景:
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其派生类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其派生类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,派生类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂派生类中的某一个,客户端在使用时可以无须关心是哪一个工厂派生类创建产品派生类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
class Factory
{
public:
virtual Operation *CreateProduct() = 0;
};
class Add_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Add_Operation();
}
};
class Sub_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Sub_Operation();
}
};
class Mul_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Mul_Operation();
}
};
class Div_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Div_Operation();
}
};
int main()
{
int a, b;
cin >> a >> b;
Add_Factory *p_fac = new Add_Factory();
Operation *p_pro = p_fac->CreateProduct();
p_pro->var1 = a;
p_pro->var2 = b;
cout << p_pro->GetResult() << endl;
Mul_Factory *p_fac1 = new Mul_Factory();
Operation *p_pro1 = p_fac1->CreateProduct();
p_pro1->var1 = a;
p_pro1->var2 = b;
cout << p_pro1->GetResult() << endl;
return 0;
}
抽象工厂模式
定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
应用场景:
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
- 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation_Pos
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
class Operation_Neg
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 + var2);
}
};
class Sub_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 - var2);
}
};
class Mul_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 * var2);
}
};
class Div_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 / var2);
}
};
// Here is the Factory class
class Factory
{
public:
virtual Operation_Pos *CreateProduct_Pos() = 0;
virtual Operation_Neg *CreateProduct_Neg() = 0;
};
class Add_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Add_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Add_Operation_Neg();
}
};
class Sub_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Sub_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Sub_Operation_Neg();
}
};
class Mul_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Mul_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Mul_Operation_Neg();
}
};
class Div_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Div_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Div_Operation_Neg();
}
};
int main()
{
int a, b;
cin >> a >> b;
Add_Factory *p_fac = new Add_Factory();
Operation_Pos *p_pro = p_fac->CreateProduct_Pos();
p_pro->var1 = a;
p_pro->var2 = b;
cout << p_pro->GetResult() << endl;
Add_Factory *p_fac1 = new Add_Factory();
Operation_Neg *p_pro1 = p_fac1->CreateProduct_Neg();
p_pro1->var1 = a;
p_pro1->var2 = b;
cout << p_pro1->GetResult() << endl;
return 0;
}
观察者模式
观察者模式:定义一种一(被观察类)对多(观察类)的关系,让多个观察对象同时监听一个被观察对象,被观察对象状态发生变化时,会通知所有的观察对象,使他们能够更新自己的状态。
观察者模式中存在两种角色:
- 观察者:内部包含被观察者对象,当被观察者对象的状态发生变化时,更新自己的状态。(接收通知更新状态)
- 被观察者:内部包含了所有观察者对象,当状态发生变化时通知所有的观察者更新自己的状态。(发送通知)
应用场景:
- 当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象有待改变时,应该考虑使用观察者模式;
- 一个抽象模型有两个方面,其中一方面依赖于另一方面,这时可以用观察者模式将这两者封装在独立的对象中使它们各自独立地改变和复用。
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Subject;
//观察者 基类 (内部实例化了被观察者的对象sub)
class Observer
{
protected:
string name;
Subject *sub;
public:
Observer(string name, Subject *sub)
{
this->name = name;
this->sub = sub;
}
virtual void update() = 0;
};
class StockObserver : public Observer
{
public:
StockObserver(string name, Subject *sub) : Observer(name, sub)
{
}
void update();
};
class NBAObserver : public Observer
{
public:
NBAObserver(string name, Subject *sub) : Observer(name, sub)
{
}
void update();
};
//被观察者 基类 (内部存放了所有的观察者对象,以便状态发生变化时,给观察者发通知)
class Subject
{
protected:
list<Observer *> observers;
public:
string action; //被观察者对象的状态
virtual void attach(Observer *) = 0;
virtual void detach(Observer *) = 0;
virtual void notify() = 0;
};
class Secretary : public Subject
{
void attach(Observer *observer)
{
observers.push_back(observer);
}
void detach(Observer *observer)
{
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end())
{
if ((*iter) == observer)
{
observers.erase(iter);
return;
}
++iter;
}
}
void notify()
{
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end())
{
(*iter)->update();
++iter;
}
}
};
void StockObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭股票,装做很认真工作的样子!" << endl;
}
}
void NBAObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭NBA,装做很认真工作的样子!" << endl;
}
}
int main()
{
Subject *dwq = new Secretary();
Observer *xs = new NBAObserver("xiaoshuai", dwq);
Observer *zy = new NBAObserver("zouyue", dwq);
Observer *lm = new StockObserver("limin", dwq);
dwq->attach(xs);
dwq->attach(zy);
dwq->attach(lm);
dwq->action = "去吃饭了!";
dwq->notify();
cout << endl;
dwq->action = "梁所长来了!";
dwq->notify();
return 0;
}