c++设计模式
设计模式
分类:
- 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
- 结构型模式:把类或对象结合在一起形成一个更大的结构。
- 行为型模式:类和对象如何交互,及划分责任和算法。
核心思想:
- 开闭原则:对扩展开放,对修改关闭,降低维护带来的新风险
- 里氏替换原则: **任何基类可以出现的地方,子类一定可以出现,只有当派生类可以替换掉基类,软件功能不受影响时,基类才能真正被复用,**而派生类也能够在基类的基础上增加新的行为。
- 依赖倒置原则:高层不应该依赖低层,要面向接口编程。在高层和底层之间插入一层抽象层,屏蔽底层细节,向上层提供统一的接口
- 单一职责原则:一个类只干一件事,实现类要单一
- 接口隔离原则:一个接口只干一件事,接口要精简单一
- 迪米特法则(最少知识原则):不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度
- 合成复用原则:尽量使用组合或者聚合关系实现代码复用,少使用继承
1 创建型之单例模式
1 单例模式
单例模式(Singleton Pattern):**确保某一个类只有一个实例,**这个类称为单例类,单例模式是一种对象创建型模式。
思路:
- **将构造函数访问权限变成private或protected,**不允许类的外部创建对象
- 提供一个静态成员函数,获取内部创建对象的地址
-
静态成员变量既可以通过对象名访问,也可以通过类名访问,
-
但要遵循 private、protected 和 public 关键字的访问权限限制。
-
当通过对象名访问时,对于不同的对象,访问的是同一份内存。
-
普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员
-
在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。
-
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。
-
private 关键字的作用在于更好地隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为 public,
-
不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private。
-
protected 成员和 private 成员类似,也不能通过对象访问
-
当存在继承关系时,protected 和 private 就不一样了:基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用
-
如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为 public 或 protected;只有那些不希望在派生类中使用的成员才声明为 private
-
如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected
-
类成员的访问权限由高到低依次为 public --> protected --> private
饱汉子模式:还未使用已创建对象了。有点浪费内存
#include <iostream>
#include <string>/* 单例模式将构造函数设置为protect设置一个静态成员*/using namespace std;class MyDemo {
public:void show(){cout << "MyDemo" << endl;}static MyDemo * get_obj(void) {cout << "MyDemo x: " << x << endl;return obj;}protected:MyDemo() {}private:static MyDemo *obj;int x = 1; // 对象的属性,类方法不能方位对象属性,因为静态方法没有隐式传入this指针
};MyDemo* MyDemo::obj = new MyDemo();int main(int argc, char **argv)
{MyDemo *obj = MyDemo::get_obj();obj->show();return 0;
}
饿汉子模式:到使用的时候才创建对象。线程不安装,需加锁
#include <iostream>
#include <string>
#include <pthread.h>using namespace std;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;template<typename T>
class MySingle {
public:static MySingle<T> * get_obj() {if (m_obj == NULL) {pthread_mutex_lock(&mutex);if (m_obj == NULL) {m_obj = new MySingle<T>;}pthread_mutex_unlock(&mutex);}return m_obj;}void show() {cout << "MySingle x: " << x << endl;}void set_x(int val) {x += val;}protected:MySingle() {cout << "MySingle initialized" << endl;}private:static MySingle<T> *m_obj;static int x;
};template<typename T>
MySingle<T> * MySingle<T>::m_obj = nullptr;template<typename T>
int MySingle<T>::x = 0;int main(int argc, char **argv)
{MySingle<int> *obj = MySingle<int>::get_obj();obj->show();obj->set_x(2);MySingle<int> *obj1 = MySingle<int>::get_obj();obj->show();obj1->show();return 0;
}
2 创建型之工厂设计模式
2.1 简单工厂
简单工厂模式实际就是所有产品类都交给一个具体的工厂进行实现,主要的好处是 :
但是当引入新的产品Product3时,就需要添加一个Product3实现类,并且修改原本的工厂类。
- 传统的方式是在使用对象的时候通过new方法进行创建,通过简单工厂模式就可以在使用时通过工厂创建对象,隐藏掉对象的细节 (将对象创建与对象使用进行解耦)。
- 缺点:在新增产品时,很明显的,我们要修改原有的工厂代码,违背了开闭原则
2.2 工厂方法
为解决简单工厂违反的开闭原则。将工厂进行抽象,没创建一个产品类的同时,创建一个对应的工厂类,一个产品类对应一个工厂类;工厂类是抽象工厂的实现
2.3 抽象工厂
相比工厂方法,一个工厂只生产一种产品,抽象工厂,一个工厂可以生产整个产品族:比如,工厂方法只能生产手机,而抽象工厂可以生产手机及与手机相关的一系列周边。
优点:
- 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么产品的创建过程。
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
- 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,违反“开闭原则”
3 结构型
3.1 适配器模式
- 对象适配器:在适配器类中,调用被适配类的对象的方法,然后将方法的返回值重新处理,按接口规范返回。
- 类适配器:利用多继承,在适配器类中,调用被适配类的方法,并重写标准接口的方法。
优点:目标类和适配器类解耦,提高程序的可扩展性,符合开闭原则
缺点:可能使系统变复杂,代码不易理解。
3.2 代理模式
- 利用继承,代理类和被代理类都是标准接口的实现;代理类在实例化时,会传入被代理类的对象的引用。代理类在调用被代理对象的方法前,做一些功能增强
优缺点:
-
代理模式能够将真正被调用的对象进行隔离,在一定程度上降低了系统的耦合度代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。
-
代理对象可以在对目标对象发出请求之前进行一个额外的增强操作,例如:权限检查等
-
由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢;而且系统会变复杂
4 行为型
4.1 观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时会通知所有观察者对象,使他们能够自动更新自己。
- **这种交互也称为发布-订阅,**发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知
优缺点:
- 观察者和被观察者之间建立了一个抽象的耦合。观察者模式支持广播通信
- 观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩
4.2 策略模式
策略模式的作用就是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立的算法类,使得他们可以相互替换。
优缺点:
-
优点:
- 各自使用封装的算法,可以容易地引入新的算法来满足相同的接口
- 算法的细节完全封装在Strategy类中,因此可以在不影响Context类的情况下更改算法的实现
- 由于实现的是同一个接口,所以策略之间可以自由切换
-
缺点:客户端必须知道所有的策略,了解它们之间的区别,以便选择恰当的算法