设计模式的特点、应用场景以及在Qt中的使用

设计模式是软件开发过程中帮助开发者解决一些常见问题的可靠方法,合理使用设计模式,可以减少代码的重复和耦合,提高代码的可重用性、可拓展性和可维护性。

设计模式可以被分为三类:创建型模式、结构型模式和行为型模式,总共23种。本文将介绍这23种设计模式的特点和应用场景,并以Qt框架中的应用来进行说明。


1、创建型模式

创建型模式主要关注如何创建对象。这些模式可以根据需要动态地创建对象,而且可以将对象的创建过程与客户端代码进行分离,从而提高系统的灵活性和可扩展性。

1.1、工厂方法模式(Factory Method Pattern)

特点:工厂方法模式通过一个工厂方法来创建对象,而不是直接使用new关键字。这样可以隐藏对象创建的细节,使得代码更加灵活、可扩展。

应用场景:工厂方法模式常用于创建复杂对象,尤其是当创建对象的过程中涉及到多个类时。比如说,一个类需要创建多个不同的对象,但是这些对象的创建过程相似,这时就可以使用工厂方法模式。

说明:工厂方法模式往往是提供一个返回父类指针的接口,接口内部创建各种该父类派生的子类,示例代码如下。

class A;
class B : public A;
class C : public A;
A *create(char *type) {
    
    
	if (strcmp(type, "b") == 0) return new B;
	else if (strcmp(type, "c") == 0) return new C;
	else return NULL;
}

1.2、抽象工厂模式(Abstract Factory Pattern)

特点:抽象工厂模式提供了一个接口,用于创建一系列相关或依赖对象。这样可以将对象的创建过程与客户端代码分离,使得系统更加灵活、可扩展。

应用场景:抽象工厂模式常用于创建一系列相关的对象,比如说GUI界面中的组件。在GUI界面中,通常需要创建一系列相互依赖的组件,比如按钮、文本框等。这时可以使用抽象工厂模式来创建这些对象。

1.3、单例模式(Singleton Pattern)

特点:单例模式确保一个类只有一个实例,并提供全局访问点。这样可以避免在系统中出现多个相同的对象,从而提高系统的性能和可维护性。

应用场景:单例模式常用于需要全局访问的对象,比如说日志记录器、数据库连接池等。

说明:常见用法是把类的构造函数设为私有,然后提供一个类的静态函数来获取单例对象,代码如下。

class A {
    
    
public:
	static A *inst() {
    
    
		static A *a = NULL;
		if (a == NULL) a = new A;
		return a;
	}
private:
	A() {
    
    }
}

1.4、原型模式(Prototype Pattern)

特点:原型模式通过复制现有的对象来创建新的对象。这样可以避免重复创建对象,从而提高系统的性能。

应用场景:原型模式常用于创建复杂对象,尤其是当对象的创建过程很耗时、耗资源时。比如说,一个复杂的对象需要从数据库中读取数据并进行计算,这时可以使用原型模式来避免重复读取数据库,提高系统的性能。

说明:一般用法就是在类里定义一个clone方法,用来创建新的对象,例程如下。

class A {
    
    
public:
	A(int data) {
    
     /* 一系列耗时很长且固定的操作 */ }
	A *clone() {
    
     
		A *a = new A;
		a->data = this->data;
		return a;
	}
private:
	A() {
    
    }
	int data;	
}

1.5、建造者模式(Builder Pattern)

特点:建造者模式将一个复杂对象的构建过程分解成多个简单的步骤,从而使得构建过程更加灵活、可控。

应用场景:建造者模式常用于创建复杂的对象,尤其是当对象的构建过程涉及到多个步骤时。比如说,一个复杂的电子产品需要进行多次装配和调试,这时可以使用建造者模式来管理这个过程。


2、结构型模式

结构型模式主要关注如何通过对象和类的组合形成更大的结构。这些模式可以帮助我们将对象和类组织起来,从而更好地管理和维护系统中的各个部分。

2.1、适配器模式(Adapter Pattern)

特点:适配器模式将一个类的接口转换成客户端所期望的另一个接口。这样可以使得原本不兼容的类能够一起工作。

应用场景:适配器模式常用于将已有的类集成到新的系统中。比如说,一个新的系统需要使用一个已有的类,但是这个类的接口与系统不兼容,这时可以使用适配器模式来将这个类转换成新系统所期望的接口。

说明:一般用在项目经过更新迭代,由于框架重构或者新增需求,旧的模块接口不能直接使用,而旧模块实现逻辑比较复杂或者混乱,不宜修改,此时可以用适配器模式,在新旧代码之间做一个适配层,以便新代码复用旧代码.

2.2、桥接模式(Bridge Pattern)

特点:桥接模式将一个对象的实现与其抽象分离开来,使得两者可以独立变化。这样可以使得系统更加灵活、可扩展。

应用场景:桥接模式常用于将系统的抽象部分和实现部分分离开来,使得它们可以独立变化。比如说,一个电子产品可以有多个型号和品牌,这时可以使用桥接模式将它们分离开来,从而使得系统更加灵活。

说明:桥接模式实际上就是把不同维度的对象组合使用,比如Qt中的绘图就是一种组合模式,看以下例程,我们要画一个红色的圆。

QPainter painter;
painter.setColor(QColor(255, 0, 0));
painter.drawEllipse(QRect(0, 0, 100, 100));

painter就起到了桥接的作用,把描述红色的QColor对象和绘制圆形的方法桥接起来。
如果不用桥接,那可能接口就会变成painter.drawRedEllipse(),绘制绿色圆时就是painter.drawGreenEllipse(),需要实现的方法就变得无穷无尽。

2.3、组合模式(Composite Pattern)

特点:组合模式将对象组合成树形结构,并以统一的方式处理这些对象。这样可以使得系统更加灵活、可扩展。

应用场景:组合模式常用于处理复杂的层次结构,比如说文件系统、GUI界面等。在这些系统中,通常需要对不同的对象进行统一处理,比如遍历、增加、删除等,这时可以使用组合模式来处理这些对象。

说明:Qt中的QWidget就用到了组合模式,每一个控件都由QWidget或其派生类派生,它们之间存在着树状的层级关系,并且都有一些共有的方法,比如QPaintEvent,setParent,move,resize等。

2.4、装饰器模式(Decorator Pattern)

特点:装饰器模式允许动态地为一个对象添加额外的功能,而不需要修改原有的类。这样可以使得系统更加灵活、可扩展。

应用场景:装饰器模式常用于为一个对象添加额外的功能,比如说给一个GUI组件添加滚动条、边框等。这种模式的好处是可以在不改变原有类结构的情况下,对其进行扩展和定制。

说明:继承父类也是实现装饰器模式的一种方式。比如Qt中继承QLineEdit实现一个带有按钮的输入框。
在这里插入图片描述

class LineEditWithButton : public QLineEdit {
    
    
public:
	LineEditWithButton(QWidget *parent = NULL) : QLineEdit(parent) {
    
    
		QHBoxLayout *layout = new QHBoxLayout(this);
	    layout->setMargin(0);
	    layout->setSpacing(0);
	    QSpacerItem *spacerItem = new QSpacerItem(150, 20, 
												  QSizePolicy::Expanding, 
												  QSizePolicy::Minimum);
	    layout->addItem(spacerItem);
	    for (int i = 0; i < 5; ++i) {
    
    
	        QPushButton *btn = new QPushButton(QString::number(i), this);
	        btn->setFixedSize(30, 30);
	        layout->addWidget(btn);
	    }
	}
}

在这里插入图片描述
既保留了QLineEdit原有的功能,又添加了按钮。

2.5、外观模式(Facade Pattern)

特点:外观模式为一组复杂的子系统提供了一个统一的接口,从而使得这个子系统更加易于使用。

应用场景:外观模式常用于隐藏复杂的系统实现细节,使得系统更加易于使用。比如说,一个电子产品有多个组件,每个组件有不同的设置和控制方式,这时可以使用外观模式来为这些组件提供一个统一的控制界面。

说明:外观模式比较常用,可以理解为一个管理者,Qt中的QThreadPool就是多个线程对象的管理者,用到了外观模式。
下面是我们平时做界面比较常用的一种外观模式的示例。

class MLabel : public QWidget {
    
    
public:
	MLabel() {
    
    
		for (int i = 0; i < 3; label[i++] = new QLabel(this)); 
		// 省略 布局
	}
	
	/* 清空所有label */
	void reset() {
    
     
		for (int i = 0; i < 3; label[i++]->setText("")); 
	}

private:
	QLabel *label[3];
}

这个例子中的reset方法就实现了外观模式,客户端使用MLabel时,不需要了解清空它内容的具体操作,只需调用reset方法。

2.6、享元模式(Flyweight Pattern)

特点:享元模式将一个类的实例分成多个共享的部分,从而减少了系统的内存占用。这样可以使得系统更加节省内存、更加高效。

应用场景:享元模式常用于处理大量相似的对象,比如说图形界面中的字母、数字等。在这些情况下,通常需要创建大量的对象,但是这些对象之间又存在大量的共同点,这时可以使用享元模式来共享这些共同点,从而减少系统的内存占用。

说明:最常见的享元模式思想就是C++的字符串常量,字符串常量在编译时就被定义在静态储存区,在程序中,只要使用了同一个字符串常量,那数据都是从同一个地址读取的。
Qt中的QString也用到了享元模式的思想,如以下例子。

QString str1 = "111";
QString str2 = "111";
QString str3 = str1;
QString str4 = str1;
qDebug() << (int)str1.constData() << 
			(int)str2.constData() << 
			(int)str3.constData() << 
			(int)str4.constData();

在这里插入图片描述
str1、str3、str4的数据地址都是一样的,他们共享了同一块内存。
QFont、QColor这些被频繁创建的类型也用到了享元。

2.7、代理模式(Proxy Pattern)

特点:代理模式为一个对象提供了一个替代品或占位符,从而可以控制对这个对象的访问。这样可以使得系统更加安全、可控。

应用场景:代理模式常用于需要对一个对象进行控制或限制访问的情况下。比如说,一个对象需要被多个线程或进程访问,但是需要进行访问控制,这时可以使用代理模式来控制对这个对象的访问。

说明:代理模式的实现方式与装饰器模式有点相似,但意图有所区别,装饰器模式是为了添加功能,而代理模式是为了控制访问。


3、行为型模式

行为型模式主要关注对象之间的通信和协作方式。这些模式可以帮助我们更好地组织对象之间的交互,从而更好地实现系统的功能。

3.1、责任链模式(Chain of Responsibility Pattern)

特点:责任链模式将一个请求沿着一条链传递,直到有一个对象可以处理这个请求为止。这样可以使得系统更加灵活、可扩展。

应用场景:责任链模式常用于处理复杂的请求处理流程,比如说一个请求需要经过多个处理环节才能得到处理。在这些情况下,使用责任链模式可以使得系统更加灵活、可扩展。

说明:Qt中的事件处理过程就是一种责任链模式,触发一个事件时,事件对象会从父控件传递到子控件,如果子控件没有吸收掉事件,事件会返回给父控件,依次传递。

3.2、命令模式(Command Pattern)

特点:命令模式将一个请求封装成一个对象,从而可以对请求进行参数化、队列化、记录日志等操作。这样可以使得系统更加灵活、可扩展。

应用场景:命令模式常用于需要将请求进行参数化、队列化、记录日志等操作的情况下。比如说,一个系统需要对多个操作进行记录,这时可以使用命令模式来记录每个操作。

说明:命令模式实现了请求发送者和接收者之间的解耦,请求发送者把操作所需的信息封装成一个对象发给接收者,接收者不需要知道它具体怎么操作,只需要调用某个固定的方法就可以完成请求。

3.3、迭代器模式(Iterator Pattern)

特点:迭代器模式提供了一种访问一个集合对象元素的方式,而不需要暴露这个对象的内部结构。这样可以使得系统更加灵活、可扩展。

应用场景:迭代器模式常用于处理集合对象的遍历操作,比如说对列表、树形结构等进行遍历。在这些情况下,使用迭代器模式可以使得系统更加灵活、可扩展。

说明:迭代器模式实际上就是对一个容器的遍历方式进行封装,平时常用的就是iterator,我们使用它来遍历容器是不用考虑容器内部的储存结构。比如map等结构比较复杂的容器,我们直接使用iterator来遍历会方便很多。

3.4、中介者模式(Mediator Pattern)

特点:中介者模式将多个对象之间的交互关系封装到一个中介者对象中,从而减少对象之间的直接耦合。这样可以使得系统更加灵活、可扩展。

应用场景:中介者模式常用于处理多个对象之间的复杂交互关系,比如说一个系统中存在多个对象,这些对象之间需要相互通信,但是通信关系又非常复杂。在这些情况下,使用中介者模式可以将这些复杂的交互关系封装到一个中介者对象中,从而减少对象之间的直接耦合。

3.5、备忘录模式(Memento Pattern)

特点:备忘录模式可以在不破坏封装性的前提下,捕获一个对象的内部状态,并将该状态保存到外部存储器中。这样可以使得系统更加灵活、可扩展。

应用场景:备忘录模式常用于需要保存对象历史状态的情况下。比如说,一个系统需要记录对象的历史状态,这时可以使用备忘录模式来保存这些历史状态。

3.6、解释器模式(Interpreter Pattern)

特点:解释器模式定义了一种语言语法的表示,并提供了解释器来解释这种语法。这样可以使得系统更加灵活、可扩展。

应用场景:解释器模式常用于处理一些需要进行解释的问题,比如说编译器、计算器等。在这些情况下,使用解释器模式可以将复杂的问题进行简化,从而使得系统更加灵活、可扩展。

3.7、观察者模式(Observer Pattern)

特点:观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知。这样可以使得系统更加灵活、可扩展。

应用场景:观察者模式常用于处理多个对象之间的状态依赖关系,比如说一个对象的状态发生变化时,需要通知多个其他对象。在这些情况下,使用观察者模式可以使得系统更加灵活、可扩展。

说明:Qt的信号槽是一种观察模式的实现。观察者模式和广播模式的区别在于,观察者存在观察者与被观察者的依赖关系,被观察者变化时,会通知观察者,而广播模式不存在观察者的概念,所有的对象都会接收到消息。

3.8、状态模式(State Pattern)

特点:状态模式定义了一种状态机的模型,可以使得一个对象在不同状态下行为不同。这样可以使得系统更加灵活、可扩展。

应用场景:状态模式常用于处理一个对象的状态变化,比如说一个对象的状态发生变化时,需要改变它的行为。在这些情况下,使用状态模式可以使得系统更加灵活、可扩展。

3.9、策略模式(Strategy Pattern)

特点:策略模式定义了一种算法的族,分别封装起来,使得它们之间可以相互替换。这样可以使得系统更加灵活、可扩展。

应用场景:策略模式常用于处理多个算法之间的选择问题,比如说一个系统需要根据不同的条件选择不同的算法。在这些情况下,使用策略模式可以使得系统更加灵活、可扩展。

说明:使用方式如下。

class A {
    
     public: virtual void run() = 0; };
class B : public A {
    
     ... };
class C : public A {
    
     ... };
A *a = NULL;

void commomRun() {
    
    
	if (a != NULL) a->run();
}

int main() {
    
    
	B *b = new B;
	C *c = new C;
	a = b;
	commomRun();		//执行B的run方法
	a = c;
	commomRun();		//执行C的run方法
}

从上例中可以看到,我们调用同一个commomRun函数,执行的效果是不一样的,这就是策略模式的思想。
在做项目时,策略模式可以和命令模式联用,把命令对象作为策略对象,提高灵活性。

3.10、模板方法模式(Template Method Pattern)

特点:模板方法模式定义了一个算法的骨架,并将一些步骤延迟到子类中实现。这样可以使得系统更加灵活、可扩展。

应用场景:模板方法模式常用于处理一些算法的骨架问题,比如说一个算法的各个步骤已经确定,但是某些步骤需要延迟到子类中实现。在这些情况下,使用模板方法模式可以使得系统更加灵活、可扩展。

说明:C++的模板类,如vector、stack、QList等容器类就是常见的模版方法模式。

3.11、访问者模式(Visitor Pattern)

特点:访问者模式可以将数据结构与数据操作分离开来,使得数据结构可以不经常发生改变而不影响操作。

应用场景:访问者模式常用于处理一些数据结构与数据操作分离的情况,比如说一个系统需要对数据结构进行操作,但是数据结构的实现经常发生变化。在这些情况下,使用访问者模式可以使得系统更加灵活、可扩展。

猜你喜欢

转载自blog.csdn.net/weixin_45001971/article/details/129611178