点击上方蓝字关注我,我们一起学编程
有任何疑问或者想看的内容,欢迎私信
微信搜索《编程笔记本》(codingbook2020),获取更多干活。
今天我们来谈一谈程序中的设计模式。
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
下面我们主要了解常用的几种设计模式:单例模式、工厂模式、观察者模式、装饰器模式。
单例模式
单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的,或使用静态实例。
应用实例:
- 一个班级只有一个班主任。
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:
- 没有接口,不能继承。
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:instance() 方法中需要使用同步锁防止多线程同时进入造成多次实例化。
实现:
常见的单例的实现形式主要有两种:懒汉模式与饿汉模式。
- 懒汉模式:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。
- 饿汉模式:饿了肯定要饥不择食,所以在单例类定义的时候就进行实例化。
单例模式的懒汉实现:构造函数声明为私有的或保护的,防止被外部函数实例化,内部保存一个私有的静态类指针保存唯一的实例,实例的动作由一个公有的类方法代劳,该方法返回单例类唯一的实例。
class singleton
{
protected:
singleton(){}
private:
static singleton* p;
public:
static singleton* instance() {
if (p == nullptr) {
p = new singleton();
}
return p;
}
};
singleton* singleton::p = nullptr;
上面这种简单的方式就是懒汉模式的单例模式,但是这种方法是线程不安全的,考虑两个线程同时首次调用 instance 方法且同时检测到 p 是 nullptr 值,则两个线程会同时构造一个实例给 p ,这是严重错误的。
单纯的懒汉模式是线程不安全,一个简单的方法是:加锁。
class singleton
{
protected:
singleton()
{
pthread_mutex_init(&mutex); // 互斥量初始化
}
private:
static singleton* p;
public:
static pthread_mutex_t mutex; // 互斥量,即锁
static singleton* initance() {
if (p == nullptr) {
pthread_mutex_lock(&mutex); // 上锁
if (p == nullptr) {
p = new singleton();
}
pthread_mutex_unlock(&mutex); // 解锁
}
return p;
}
};
singleton* singleton::p = nullptr;
另一种实现方式使用内部静态变量:该方法很容易实现,在 instance 函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。
class singleton
{
protected:
singleton()
{
pthread_mutex_init(&mutex);
}
public:
static pthread_mutex_t mutex;
static singleton* initance() {
pthread_mutex_lock(&mutex);
static singleton obj;
pthread_mutex_unlock(&mutex);
return &obj;
}
};
单例模式的饿汉实现:简单的懒汉模式是线程不安全的,因此需要加锁。但饿汉模式本来就是线程安全的,所以不用加锁。原因很简单,因为的类实例创建(对象创建)放在类外了,因此只有唯一的一个实例。
class singleton
{
protected:
singleton() {}
private:
static singleton* p;
public:
static singleton* initance() {
return p;
}
};
singleton* singleton::p = new singleton;
工厂模式
工厂模式(Factory Pattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
应用实例:
- 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
- Hibernate 换数据库只需换方言和驱动就可以。
优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
- 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景:
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
实现:
下面的演示示例:ProductA、ProductB 和 ProductC 继承自 Product 抽象类,Show 方法是不同产品的自描述;Factory 依赖于 ProductA、ProductB 和 ProductC ,Factory 根据不同的条件创建不同的 Product 对象。
#include <iostream>
#include <vector>
using namespace std;
typedef enum ProductTypeTag {
TypeA,
TypeB,
TypeC,
} PRODUCTTYPE;
// 产品类(抽象类)
class Product {
public:
virtual void Show() = 0;
};
class ProductA : public Product {
public:
void Show() {
cout << "I'm ProductA" << endl;
}
};
class ProductB : public Product {
public:
void Show() {
cout << "I'm ProductB" << endl;
}
};
class ProductC : public Product {
public:
void Show() {
cout << "I'm ProductC" << endl;
}
};
// 工厂类
class Factory {
public:
Product* CreateProduct(PRODUCTTYPE type) {
switch (type) {
case TypeA:
return new ProductA();
case TypeB:
return new ProductB();
case TypeC:
return new ProductC();
default:
return NULL;
}
}
};
int main()
{
// 先创建一个工厂类对象
Factory *ProductFactory = new Factory();
// 创建不同的产品对象
Product *productObjA = ProductFactory->CreateProduct(TypeA);
if (productObjA != NULL) {
productObjA->Show();
}
Product *productObjB = ProductFactory->CreateProduct(TypeB);
if (productObjB != NULL) {
productObjB->Show();
}
Product *productObjC = ProductFactory->CreateProduct(TypeC);
if (productObjC != NULL) {
productObjC->Show();
}
delete ProductFactory;
ProductFactory = NULL;
delete productObjA;
productObjA = NULL;
delete productObjB;
productObjB = NULL;
delete productObjC;
productObjC = NULL;
return 0;
}
/*
运行结果:
I'm ProductA
I'm ProductB
I'm ProductC
*/
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
应用实例:
- 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
- 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A 对象的行为将影响B对象,B 对象的行为将影响 C 对象 … ,可以使用观察者模式创建一种链式触发机制。
注意事项:
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
实现:
下面的演示示例:在教室里老师还没有来,同学都在干着各的事情:小张正在打游戏,小李正在抄作业 … ,现在同学们要求班长当卧底,监视老师,当老师来了通知大家一声。然后打游戏的马上停止,抄作业的也停止。
#include <iostream>
#include <list>
using namespace std;
// 观察者(抽象类)
class Observer {
public:
virtual void signal() = 0;
};
class SubjectA : public Observer {
public:
void signal() {
action();
}
void action() {
cout << "Zhang stops copying homework" << endl;
}
};
class SubjectB : public Observer {
public:
void signal() {
action();
}
void action() {
cout << "Li stops playing games" << endl;
}
};
class Notifier {
public:
virtual void registerObserver(Observer *l) = 0;
virtual void removeObserver(Observer *l) = 0;
virtual void notify() = 0;
};
class MonitorNotifier:public Notifier {
public:
void registerObserver(Observer *o) {
observers.push_back(o);
}
void removeObserver(Observer *o) {
for (list<Observer*>::iterator it = observers.begin(); it != observers.end(); it++) {
if (*it == o) {
observers.remove(o);
break;
}
}
}
void notify() {
for(list<Observer*>::iterator it = observers.begin(); it != observers.end(); it++) {
(*it)->signal();
}
}
private:
list<Observer*> observers;
};
int main()
{
// 生成一个观察者
MonitorNotifier monitor;
cout << "-------------- 1 --------------" << endl;
SubjectA a;
monitor.registerObserver(&a);
SubjectB b;
monitor.registerObserver(&b);
// 通知
monitor.notify();
cout << "--------------- 2 --------------" << endl;
monitor.removeObserver(&a);
monitor.notify();
return 0;
}
/*
运行结果:
-------------- 1 --------------
Zhang stops copying homework
Li stops playing games
--------------- 2 --------------
Li stops playing games
*/
装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般地,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例:
- 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
- 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:
- 装饰类和被装饰类可以独立发展,不会相互耦合。
- 装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:
- 多层装饰比较复杂。
使用场景:
- 扩展一个类的功能。
- 动态增加功能,动态撤销。
注意事项:可代替继承。
实现:
下面的演示示例:有一个手机,允许你为手机添加特性,比如增加挂件、屏幕贴膜等。一种灵活的设计方式是,将手机嵌入到另一对象中,由这个对象完成特性的添加,我们称这个嵌入的对象为装饰。
#include <iostream>
#include <string>
using namespace std;
class Phone {
public :
Phone() {}
virtual ~Phone() {}
virtual void showDecorate() {}
};
//具体手机类
class iPhone : public Phone {
private:
string name;
public:
iPhone(string _name) : name(_name) {}
~iPhone() {}
void showDecorate() {
cout << name <<" 的装饰如下:"<< endl;
}
};
class Nokia : public Phone {
private:
string name;
public :
Nokia(string _name) : name(_name) {}
~Nokia() {}
void showDecorate() {
cout << name <<" 的装饰如下:"<< endl;
}
};
class DecoratorPhone : public Phone
{
private :
Phone* m_phone; //要装饰的手机
public:
DecoratorPhone(Phone *phone) : m_phone(phone) {}
virtual void showDecorate() {
m_phone->showDecorate();
}
};
//具体的装饰A
class DecoratePhoneA : public DecoratorPhone {
public :
DecoratePhoneA(Phone* ph) : DecoratorPhone(ph) {}
void showDecorate() {
DecoratorPhone::showDecorate();
AddDecorate();
}
private:
void AddDecorate() {
cout <<"挂件" << endl;
}
};
class DecoratePhoneB : public DecoratorPhone {
public:
DecoratePhoneB(Phone* ph) : DecoratorPhone(ph) {}
void showDecorate() {
DecoratorPhone::showDecorate();
AddDecorate();
}
private:
void AddDecorate() {
cout << "贴膜"<< endl;
}
};
int main()
{
Phone* ph1 = new iPhone("iphone 11");
Phone* ph2 = new Nokia("Nokia 5230");
Phone *dp1 = new DecoratePhoneA(ph1); //增加挂件
Phone* dp2 = new DecoratePhoneB(ph2); //增加贴膜
dp1->showDecorate();
dp2->showDecorate();
delete ph1;
delete ph2;
delete dp1;
delete dp2;
return 0;
}
/*
运行结果:
iphone 11 的装饰如下:
挂件
Nokia 5230 的装饰如下:
贴膜
*/