一.设计模式
1.设计模式理解
C++程序设计模式是针对在C++编程中常见问题的通用、可重用的解决方案。设计模式主要是在软件工程领域,特别是在面向对象编程中发展起来的,它们提供了一种标准的术语和解决方案,可以帮助程序员更好地设计软件架构和组件。
设计模式通常分为三大类,总共有23种,它们分别是:
- 创建型模式(Creational Patterns):这类模式处理对象创建机制,旨在提高对象的创建过程的灵活性和重用性。它们共有5种:
- 工厂方法模式(Factory Method):定义一个接口用于创建对象,但让子类决定实例化哪个类。
- 抽象工厂模式(Abstract Factory):创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
- 建造者模式(Builder):将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 结构型模式(Structural Patterns):这类模式关注类和对象组合的方式,用于形成更大的结构。它们共有7种:
- 适配器模式(Adapter):将一个类的接口转换成客户期望的另一个接口。
- 装饰器模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活。
- 代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
- 外观模式(Facade):为一组复杂的子系统提供一个统一的接口。
- 桥接模式(Bridge):将抽象部分与实现部分分离,使它们都可以独立地变化。
- 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。
- 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
- 行为型模式(Behavioral Patterns):这类模式负责对象之间的有效沟通和责任分配,有助于提高系统的灵活性。它们共有11种:
- 策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。
- 模板方法模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
- 观察者模式(Observer):对象间的一对多依赖关系,当一个对象改变状态,所有依赖于它的对象都会得到通知并自动更新。
- 状态模式(State):允许对象在内部状态改变时改变它的行为。
- 命令模式(Command):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。
- 责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免了请求发送者和接收者之间的耦合关系。
- 中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。
- 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素, 而又不暴露该对象的内部表示。
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 备忘录模式(Memento):捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
- 解释器模式(Interpreter):为语言创建解释器,用来解释该语言中的句子。
2.类图和UML
类图是面向对象设计中用来描述系统中类的结构和类之间关系的一种静态结构图。它展示了系统的静态视图,即在一个特定的时间点,系统中的类、接口、协作以及它们之间的关系。
类图用于描述设计模式的原因是,设计模式主要是在面向对象编程的背景下定义的,它们依赖于类和对象的结构和关系。通过类图,设计模式的结构可以直观地表示出来,使得设计意图更加清晰,有助于理解和交流。
UML(统一建模语言)是表示类图的一种标准方式。UML
提供了一套丰富的符号和图形,用于表示面向对象系统中的各种元素,包括类、接口、属性、方法、关联、泛化(继承)等。UML
类图是设计人员和开发人员之间沟通的重要工具,因为它提供了一种通用的、易于理解的语言来描述系统的设计。
3.认识UML类图
可以看到该图分为上中下三部分:上层是类名(红色),中间层是属性(类的成员变量,橙色),下层是方法(类的成员函数,蓝色)。
-
可见性:+ 表示public、# 表示protected、- 表示private、__(下划线)表示static
-
属性的表示方式:【可见性】【属性名称】:【类型】= { 缺省值,可选 }
-
方法的表示方式:【可见性】【方法名称】(【参数名 : 参数类型,……】):【返回值类型】
如果我们定义的类是一个抽象类(类中有纯虚函数),在画UML
类图的时候,类名需要使用斜体显示。
4.类和类之间的关系
UML
描述类之间的关系:
-
继承也叫作泛化(Generalization),用于描述父子类之间的关系,父类又称为基类或者超类,子类又称作派生类。在
UML
中,泛化关系用带空心三角形的实线来表示,其中三角形的箭头指向的是父类,实现末尾连接的是派生类。 -
类之间的关联关系有三种,分别是:单向关联、双向关联、自关联
- 单向关联指的是关联只有一个方向,比如每个孩子(Child)都拥有一个父亲(Parent),可以理解为一种继承,如果是单向关联,使用的连接线是带单向箭头的实线, 哪个类作为了当前类的成员变量,那么箭头就指向哪个类。上面孩子有父亲,所以箭头是孩子指向父亲
- 双向关联指的是两个类互相包含,例如孩子有父亲,父亲也有孩子,在画
UML
类图的时候,一般使用没有箭头的实线来连接有双向关联关系的两个类,这两个类的对象分别作为了对方类的成员变量。 - 自关联指的就是当前类中包含一个自身类型的对象成员,这在链表中非常常见,单向链表中都会有一个指向自身节点类型的后继指针成员,而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。一般使用带箭头的实线来描述自关联关系,箭头出来还是指向自己。
-
聚合(Aggregation)关系表示整体与部分的关系。在聚合关系中,成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在。在
UML
中,聚合关系用带空心菱形直线表示,下面举两个聚合关系的例子:汽车(Car)与 引擎(Engine)、轮胎(Wheel)、车灯(Light) -
组合(Composition)关系也表示的是一种整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也不存在,整体对象和成员对象之间具有同生共死的关系。在
UML
中组合关系用带实心菱形的直线表示,下面举两个组合关系的例子:头(Head)和 嘴巴(Mouth)、鼻子(Nose)、耳朵(Ear)、眼睛(Eye) -
依赖(Dependency)关系是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系,大多数情况下依赖关系体现在某个类的方法使用另一个类的对象作为参数。在
UML
中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方,
二.创建型模式
1.简单工厂模式
简单工厂模式并不属于创建型模式里的一种,之所以在这里介绍是为了更好的理解后面的设计模式。
通过场景来理解:有一些类A、B……,如果现在要创建一个A类的对象,但是其构造实例特别复杂,比如先读取文件,再读取数据库,再……反正一系列复杂操作,导致构建实例非常复杂,同理其余类也类似,那么可以有这么一种方式,例如先构造一个工厂类,封装一个构造方法,传入你要构建类的参数,这样工程就可以自动建立对应对象的实例。
优点:
- 客户端调用与具体实现类解耦
- 对于某些常见对象比较复杂的,就无需考虑
缺点:
- 增加新功能是通过修稿源代码实现的,不符合开闭原则
- 工厂类职责过重,一旦出现问题,大面积受到影响
2.工厂方法模式
工厂方法模式的场景理解:在上面简单工厂模式的场景基础上,每一个类对象的构造分别对应一个具体工厂,这样也可以使用到抽象工厂,每当建立一个类的对象,就new
一个对应的具体工厂。
优点:符合开闭性原则
缺点:类增加,工厂也增加,复杂和成本问题
3.抽象工厂模式
抽象工厂模式场景理解:结合上图,其实是提取共同属性,减少具体工厂,来避免工厂方法模式的复杂度问题,而又根据这个属性对应一个具体工厂,相当于简单工厂模式和工厂方法模式的结合。
4.单例模式
单例模式:某个类的对象个数只能为1。
如果要满足类对象只能为1个,那么肯定构造函数不能被多次调用,也就是构造函数私有化。
实现单例模式的步骤:
- 1.构造函数私有化
- 2.增加静态私有的当前类的指针变量
- 3.提供静态对外接口,可以让用户获得单例对象
#include <iostream>
using namespace std;
class A {
private:
A()
{
a = new A;
}
public:
static A* getinstance()
{
cout << "123" << endl;
return a;
}
private:
static A* a;
};
A* A::a = NULL;
int main()
{
A::getinstance();
//A a; //报错,因为构造函数私有化
A::getinstance();
return 0;
}
这只是一个单例模式的例子,并不符合规范,通过外面的公共接口getinstance
来初始化。
而实际单例模式创建的时候分为懒汉式和饿汉式:
#include <iostream>
using namespace std;
//懒汉式
class Lazy
{
private:
Lazy()
{
cout << "我是懒汉" << endl;
}
static Lazy* getinstance()
{
if (lazy == NULL) //懒汉:如果没有那就建立一个
{
lazy = new Lazy;
}
return lazy;
}
private:
static Lazy* lazy;
};
Lazy* Lazy::lazy=NULL;
//饿汉式
class Hungry
{
private:
Hungry() { cout << "我是饿汉" << endl; }
static Hungry* getinstance()
{
return hungry; //饿汉:类外直接创建了一个
}
private:
static Hungry* hungry;
};
Hungry* Hungry::hungry = new Hungry;
int main()
{
cout << 123 << endl;
return 0;
}
懒汉式讲究的是需要的时候再建立,饿汉是讲究的是直接一开始就建立一个对象,通过上述代码我们可以发现,饿汉式在执行main
之前就开始运行啦。