Characteristics, application scenarios and use of design patterns in Qt

Design patterns are a reliable method to help developers solve some common problems in the software development process. Proper use of design patterns can reduce code duplication and coupling, and improve code reusability, scalability, and maintainability.

Design patterns can be divided into three categories: creational patterns, structural patterns and behavioral patterns, a total of 23 types. This article will introduce the characteristics and application scenarios of these 23 design patterns, and illustrate them with applications in the Qt framework.


1. Creation mode

Creational patterns focus primarily on how to create objects. These patterns can dynamically create objects as needed and can separate the object creation process from client code, thereby improving the flexibility and scalability of the system.

1.1. Factory Method Pattern

Features: The factory method pattern creates objects through a factory method instead of directly using the new keyword. This hides the details of object creation and makes the code more flexible and extensible.

Application scenarios: The factory method pattern is often used to create complex objects, especially when multiple classes are involved in the process of object creation. For example, if a class needs to create multiple different objects, but the creation process of these objects is similar, then the factory method pattern can be used.

Note: The factory method pattern often provides an interface that returns a parent class pointer, and creates various subclasses derived from the parent class inside the interface. The sample code is as follows.

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

Features: The abstract factory pattern provides an interface for creating a series of related or dependent objects. This separates the object creation process from the client code, making the system more flexible and scalable.

Application scenarios: The abstract factory pattern is often used to create a series of related objects, such as components in a GUI interface. In a GUI interface, it is usually necessary to create a series of interdependent components, such as buttons, text boxes, etc. At this time, you can use the abstract factory pattern to create these objects.

1.3. Singleton Pattern

Features: The singleton pattern ensures that a class has only one instance and provides a global access point. This can avoid multiple identical objects appearing in the system, thereby improving the performance and maintainability of the system.

Application scenarios: Singleton mode is often used for objects that require global access, such as loggers, database connection pools, etc.

Note: A common usage is to make the constructor of the class private, and then provide a static function of the class to obtain the singleton object. The code is as follows.

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

1.4. Prototype Pattern

Features: Prototype pattern creates new objects by copying existing objects. This avoids repeated creation of objects, thereby improving system performance.

Application scenarios: The prototype pattern is often used to create complex objects, especially when the object creation process is time-consuming and resource-intensive. For example, if a complex object needs to read data from the database and perform calculations, the prototype mode can be used to avoid repeated reading of the database and improve system performance.

Note: The general usage is to define a clone method in the class to create new objects. The routine is as follows.

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

Features: The builder pattern decomposes the construction process of a complex object into multiple simple steps, making the construction process more flexible and controllable.

Application scenarios: The builder pattern is often used to create complex objects, especially when the object's construction process involves multiple steps. For example, if a complex electronic product requires multiple assembly and debugging, the builder pattern can be used to manage this process.


2. Structural model

Structural patterns focus on how to form larger structures through the combination of objects and classes. These patterns can help us organize objects and classes to better manage and maintain various parts of the system.

2.1. Adapter Pattern

Features: The adapter pattern converts the interface of a class into another interface expected by the client. This allows otherwise incompatible classes to work together.

Application scenarios: The adapter pattern is often used to integrate existing classes into new systems. For example, a new system needs to use an existing class, but the interface of this class is incompatible with the system. In this case, the adapter pattern can be used to convert this class into the interface expected by the new system.

Note: Generally used when the project has been updated and iterated. Due to framework reconstruction or new requirements, the old module interface cannot be used directly, and the implementation logic of the old module is complex or confusing and should not be modified. At this time, the adapter mode can be used to combine the old and new code. Create an adaptation layer between them so that new code can reuse old code.

2.2. Bridge Pattern

Features: The bridge pattern separates the implementation of an object from its abstraction so that the two can change independently. This can make the system more flexible and scalable.

Application scenarios: The bridge pattern is often used to separate the abstract part and the implementation part of the system so that they can change independently. For example, an electronic product can have multiple models and brands. In this case, the bridge mode can be used to separate them, making the system more flexible.

Note: The bridge mode is actually a combination of objects of different dimensions. For example, drawing in Qt is a combination mode. Looking at the following routine, we have to draw a red circle.

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

Painter plays a bridging role, bridging the QColor object describing red and the method of drawing a circle.
If there is no need for bridging, the interface may become painter.drawRedEllipse(). When drawing a green circle, it is painter.drawGreenEllipse(), and the methods that need to be implemented become endless.

2.3. Composite Pattern

Features: Combination mode combines objects into a tree structure and processes these objects in a unified manner. This can make the system more flexible and scalable.

Application scenarios: The combination mode is often used to deal with complex hierarchical structures, such as file systems, GUI interfaces, etc. In these systems, different objects usually need to be processed uniformly, such as traversing, adding, deleting, etc. In this case, the combination mode can be used to process these objects.

Note: QWidget in Qt uses the combination mode. Each control is derived from QWidget or its derived class. There is a tree-like hierarchical relationship between them, and they all have some common methods, such as QPaintEvent, setParent, move, resize etc.

2.4. Decorator Pattern

Features: The decorator pattern allows you to dynamically add additional functionality to an object without modifying the original class. This can make the system more flexible and scalable.

Application scenarios: The decorator pattern is often used to add additional functions to an object, such as adding scroll bars, borders, etc. to a GUI component. The advantage of this pattern is that it can be extended and customized without changing the original class structure.

Note: Inheriting parent classes is also a way to implement the decorator pattern. For example, Qt inherits QLineEdit to implement an input box with buttons.
Insert image description here

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);
	    }
	}
}

Insert image description here
It not only retains the original functions of QLineEdit, but also adds buttons.

2.5. Facade Pattern

Features: The appearance pattern provides a unified interface for a complex set of subsystems, making this subsystem easier to use.

Application scenarios: Appearance patterns are often used to hide complex system implementation details, making the system easier to use. For example, if an electronic product has multiple components, and each component has different settings and control methods, the appearance mode can be used to provide a unified control interface for these components.

Note: The appearance mode is commonly used and can be understood as a manager. QThreadPool in Qt is the manager of multiple thread objects and uses the appearance mode.
The following is an example of an appearance mode that we usually use when making interfaces.

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];
}

The reset method in this example implements the appearance mode. When the client uses MLabel, it does not need to know the specific operation of clearing its content. It only needs to call the reset method.

2.6. Flyweight Pattern

Features: Flyweight mode divides instances of a class into multiple shared parts, thereby reducing the memory footprint of the system. This can make the system more memory-saving and more efficient.

Application scenarios: Flyweight mode is often used to process a large number of similar objects, such as letters, numbers, etc. in graphical interfaces. In these cases, it is usually necessary to create a large number of objects, but there are a lot of commonalities between these objects. In this case, the flyweight mode can be used to share these commonalities, thereby reducing the memory footprint of the system.

Note: The most common flyweight mode idea is the string constant of C++. String constants are defined in the static storage area during compilation. In the program, as long as the same string constant is used, the data will come from the same The address is read.
QString in Qt also uses the idea of ​​flyweight mode, as shown in the following example.

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();

Insert image description here
The data addresses of str1, str3, and str4 are all the same, and they share the same memory.
Frequently created types such as QFont and QColor also use flyweights.

2.7. Proxy Pattern

Features: The proxy pattern provides a substitute or placeholder for an object, thereby controlling access to the object. This can make the system more secure and controllable.

Application scenarios: The proxy mode is often used when it is necessary to control or restrict access to an object. For example, if an object needs to be accessed by multiple threads or processes, but access control is required, the proxy mode can be used to control access to the object.

Note: The implementation of the proxy mode is somewhat similar to the decorator mode, but the intention is different. The decorator mode is to add functionality, while the proxy mode is to control access.


3. Behavioral model

Behavioral patterns focus on how objects communicate and collaborate. These patterns can help us better organize the interactions between objects and thus better implement the functions of the system.

3.1. Chain of Responsibility Pattern

Features: The chain of responsibility pattern passes a request along a chain until an object can handle the request. This can make the system more flexible and scalable.

Application scenarios: The chain of responsibility model is often used to handle complex request processing processes. For example, a request needs to go through multiple processing links before it can be processed. In these cases, using the chain of responsibility model can make the system more flexible and scalable.

Note: The event processing process in Qt is a chain of responsibility model. When an event is triggered, the event object will be passed from the parent control to the child control. If the child control does not absorb the event, the event will be returned to the parent control and passed in turn.

3.2. Command Pattern

Features: The command mode encapsulates a request into an object, so that the request can be parameterized, queued, logged, etc. This can make the system more flexible and scalable.

Application scenarios: Command mode is often used when requests need to be parameterized, queued, logged, etc. For example, if a system needs to record multiple operations, the command mode can be used to record each operation.

Description: The command mode realizes the decoupling between the request sender and the receiver. The request sender encapsulates the information required for the operation into an object and sends it to the receiver. The receiver does not need to know how it operates specifically, and only needs to call a certain A fixed method can complete the request.

3.3. Iterator Pattern

Features: The iterator pattern provides a way to access the elements of a collection object without exposing the internal structure of the object. This can make the system more flexible and scalable.

Application scenarios: The iterator pattern is often used to process traversal operations of collection objects, such as traversing lists, tree structures, etc. In these cases, using the iterator pattern can make the system more flexible and scalable.

Note: The iterator pattern actually encapsulates the traversal method of a container. The commonly used iterator is iterator. We use it to traverse the container without considering the internal storage structure of the container. For example, for containers with relatively complex structures such as maps, it will be much more convenient for us to directly use iterators to traverse.

3.4. Mediator Pattern

Features: The mediator pattern encapsulates the interaction between multiple objects into a mediator object, thereby reducing the direct coupling between objects. This can make the system more flexible and scalable.

Application scenarios: The mediator pattern is often used to deal with complex interactive relationships between multiple objects. For example, there are multiple objects in a system. These objects need to communicate with each other, but the communication relationship is very complex. In these cases, using the mediator pattern can encapsulate these complex interactions into a mediator object, thereby reducing the direct coupling between objects.

3.5. Memento Pattern

Features: Memento mode can capture the internal state of an object and save the state to external memory without destroying encapsulation. This can make the system more flexible and scalable.

Application scenarios: The memo mode is often used when the historical state of an object needs to be saved. For example, if a system needs to record the historical status of objects, the memo mode can be used to save these historical statuses.

3.6. Interpreter Pattern

Features: The interpreter pattern defines a representation of the grammar of a language and provides an interpreter to interpret this grammar. This can make the system more flexible and scalable.

Application scenarios: The interpreter mode is often used to deal with problems that require interpretation, such as compilers, calculators, etc. In these cases, using the interpreter pattern can simplify complex problems, making the system more flexible and scalable.

3.7. Observer Pattern

Features: The observer pattern defines a one-to-many dependency relationship. When the state of an object changes, all objects that depend on it will be notified. This can make the system more flexible and scalable.

Application scenarios: The observer pattern is often used to handle state dependencies between multiple objects. For example, when the state of an object changes, multiple other objects need to be notified. In these cases, using the observer pattern can make the system more flexible and scalable.

Note: Qt's signal slot is an implementation of observation mode. The difference between the observer mode and the broadcast mode is that the observer has a dependency relationship between the observer and the observed. When the observed changes, the observer will be notified, while the broadcast mode does not have the concept of an observer, and all objects will receive it. information.

3.8. State Pattern

Features: The state pattern defines a state machine model that can make an object behave differently in different states. This can make the system more flexible and scalable.

Application scenarios: The state pattern is often used to handle state changes of an object. For example, when the state of an object changes, its behavior needs to be changed. In these cases, using the state pattern can make the system more flexible and scalable.

3.9. Strategy Pattern

Features: Strategy pattern defines a family of algorithms, which are encapsulated separately so that they can be replaced with each other. This can make the system more flexible and scalable.

Application scenarios: The strategy pattern is often used to deal with the selection problem between multiple algorithms. For example, a system needs to select different algorithms based on different conditions. In these cases, using the strategy pattern can make the system more flexible and scalable.

Instructions: How to use it is as follows.

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方法
}

As you can see from the above example, when we call the same commomRun function, the execution effects are different. This is the idea of ​​the strategy pattern.
When working on a project, the strategy mode can be used in conjunction with the command mode to use the command object as a strategy object to improve flexibility.

3.10. Template Method Pattern

Features: The template method pattern defines the skeleton of an algorithm and defers some steps to subclasses. This can make the system more flexible and scalable.

Application scenarios: The template method pattern is often used to deal with the skeleton problems of some algorithms. For example, the various steps of an algorithm have been determined, but some steps need to be delayed to subclasses. In these cases, using the template method pattern can make the system more flexible and scalable.

Note: C++ template classes, such as vector, stack, QList and other container classes, are common template method patterns.

3.11. Visitor Pattern

Features: The visitor pattern can separate the data structure from data operations, so that the data structure can be changed infrequently without affecting the operation.

Application scenarios: The visitor pattern is often used to deal with situations where some data structures and data operations are separated. For example, a system needs to operate the data structure, but the implementation of the data structure often changes. In these cases, using the visitor pattern can make the system more flexible and scalable.

Supongo que te gusta

Origin blog.csdn.net/weixin_45001971/article/details/129611178
Recomendado
Clasificación