常见设计模式--通俗易懂版

一、设计模式原则

1.单一职责原则

核心:一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。 

思想:如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

单一职责原则注意事项和细节

1) 降低类的复杂度,一个类只负责一项职责。

2) 提高类的可读性,可维护性

3) 降低变更引起的风险

4) 通常情况下, 我们应当遵守单一职责原则 ,只有逻辑足够简单,才可以在代码级违

2.开闭原则

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。

子类重写父类的虚函数

3.依赖倒置原则

  • 抽象不应该依赖于细节,细节应当依赖于抽象
  • 高层模块不应该依赖低层模块,两个都应该依赖于抽象
  • 依赖倒转 ( 倒置 ) 的中心思想是面向接口编程
  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去实现

依赖倒转原则的注意事项和细节

1) 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好 .

2) 变量的声明类型尽量是抽象类或接口 , 这样我们的变量引用和实际对象间,就存在

一个缓冲层,利于程序扩展和优化

  1. 继承时遵循里氏替换原则

4.里氏替换原则

子类对象能够随时随地替换父类对象,并且替换完之后,语法不会报错,业务逻辑也不会出现问题,即继承父类而不去改变父类

二、创建型模式

1.单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

1.1饿汉模式

#include <iostream>
using namespace std;
//定义一个单例模式的任务队列
class TaskQueue{
public:TaskQueue(const TaskQueue& t) = delete;TaskQueue& operator =(const TaskQueue& t)=delete;static TaskQueue* gettaskQ(){return m_taskQ;}void print(){cout<<"我是单例模式的一个成员函数"<<endl;}
private:TaskQueue()=default;//TaskQueue(const TaskQueue &t) = default;//TaaskQueue& operator =(const TaskQueue &t)=default;//只能通过类名访问静态属性或方法static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ=new TaskQueue;
int main(){TaskQueue* taskQ=TaskQueue::gettaskQ();taskQ->print();return 0;
}

1.2懒汉模式

  1. 使用双检锁解决线程安全问题
#include <iostream>
#include<mutex>
using namespace std;
//定义一个单例模式的任务队列
class TaskQueue{
public:TaskQueue(const TaskQueue& t) = delete;TaskQueue& operator =(const TaskQueue& t)=delete;static TaskQueue* gettaskQ(){if(m_taskQ==nullptr){m_mutex.lock();if(m_taskQ==nullptr){m_taskQ=new TaskQueue;}m_mutex.unlock();}return m_taskQ;
}void print(){cout<<"我是单例模式的一个成员函数"<<endl;}
private:TaskQueue()=default;//TaskQueue(const TaskQueue &t) = default;//TaaskQueue& operator =(const TaskQueue &t)=default;//只能通过类名访问静态属性或方法static TaskQueue* m_taskQ;static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ=nullptr;
mutex TaskQueue::m_mutex;
int main(){TaskQueue* taskQ=TaskQueue::gettaskQ();taskQ->print();return 0;
}

但是实际上m_taskQ = new TaskQueue;在执行过程中对应的机器指令可能会被重新排序。正常过程如下:

  • 第一步:分配内存用于保存TaskQueue对象。
  • 第二步:在分配的内存中构造一个TaskQueue 对象(初始化内存)。
  • 第三步:使用m_taskQ指针指向分配的内存。

但是被重新排序以后执行顺序可能会变成这样:

  • 第一步:分配内存用于保存TaskQueue 对象。

第二步:使用m_taskQ指针指向分配的内存。

第三步:在分配的内存中构造一个TaskQueue对象(初始化内存)

这样重排序并不影响单线程的执行结果,但是在多线程中就会出问题。如果线程A按照第二种顺序执行机器指令,执行完前两步之后失去CPU时间片被挂起了,此时线程B在第3行处进行指针判断的时候m_taskQ指针是不为空的,但这个指针指向的内存却没有被初始化,最后线程B使用了一个没有被初始化的队列对象就出问题了(出现这种情况是概率问题,需要反复的大量测试问题才可能会出现)。

在C++11中引入了原子变量 atmic,通过原子变量可以实现一种更安全的懒汉模式的单例,代码如下:

2.原子变量解决双检锁定问题

#include <iostream>
#include<mutex>
#include<atomic>
using namespace std;
//定义一个单例模式的任务队列
class TaskQueue{
public:TaskQueue(const TaskQueue& t) = delete;TaskQueue& operator =(const TaskQueue& t)=delete;static TaskQueue* gettaskQ(){TaskQueue* task = m_taskQ.load();if(task==nullptr){m_mutex.lock();task=m_taskQ.load();if(task==nullptr){task=new TaskQueue;m_taskQ.store(task);}m_mutex.unlock();}return task;}void print(){cout<<"我是单例模式的一个成员函数"<<endl;}
private:TaskQueue()=default;//TaskQueue(const TaskQueue &t) = default;//TaaskQueue& operator =(const TaskQueue &t)=default;//只能通过类名访问静态属性或方法//static TaskQueue* m_taskQ;static mutex m_mutex;static atomic<TaskQueue*> m_taskQ; 
};
//TaskQueue* TaskQueue::m_taskQ=nullptr;
mutex TaskQueue::m_mutex;
atomic <TaskQueue*> TaskQueue::m_taskQ;
int main(){TaskQueue* taskQ=TaskQueue::gettaskQ();taskQ->print();return 0;
}

上面代码中使用原子变量atomic的 store()方法来存储单例对象,使用load()方法.来加载单例对象。在原子变量中这两个函数在处理指令的时候默认的原子顺序是memory_order_seq_cst(顺序原子操作 –sequentiallyconsistent),使用顺序约束原子操作库,整个函数执行都将保证顺序执行,并且不会出现数据竞态(dataraces),不足之处就是使用这种方法实现的懒汉模式的单例执行效率更低一些。

3.使用局部静态对象解决线程安全问题---》编译器必须支持C++11

#include <iostream>using namespace std;
//定义一个单例模式的任务队列
class TaskQueue{
public:TaskQueue(const TaskQueue& t) = delete;TaskQueue& operator =(const TaskQueue& t)=delete;static TaskQueue* gettaskQ(){static TaskQueue task;return &task; //因为返回的是指针,创建的是对象,所以要取地址}void print(){cout<<"我是单例模式的一个成员函数"<<endl;}
private:TaskQueue()=default;//TaskQueue(const TaskQueue &t) = default;//TaaskQueue& operator =(const TaskQueue &t)=default;};
int main(){TaskQueue* taskQ=TaskQueue::gettaskQ();taskQ->print();return 0;
}

可行的原因:是因为在C++11标准中有如下规定,并且这个操作是在编译时由编译器保证的:

如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。

1.3懒汉模式和饿汉模式的区别

懒汉模式的缺点在创建实例对象的时候有安全问题,但这样可以减少内存的浪费〈如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。

2.简单工厂模式

简单工厂模式相关类的创建和使用步骤如下:

1.创建一个新的类,可以将这个类称之为工厂类。对于简单工厂模式来说,需要的工厂类只有一个。

2.在这个工厂类中添加一个公共的成员函数,通过这个函数来创建我们需要的对象,关于这个函数一般将其称之为工厂函数。

3.关于使用,首先创建一个工厂类对象,然后通过这个对象调用工厂函数,这样就可以生产出一个指定类型的实例对象了。

优点:

本着高内聚低耦合的原则,将系统的逻辑部分和功能分开。

缺点:

简单工厂模式会增加系统类的个数,在一定程度上增加了系统的复杂度和理解难度;

系统扩展难,一旦增加新产品,就需要修改工厂逻辑,不利于系统的扩展与维护;简单工厂模式中所有产品的创建都是由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间耦合度高,严重影响了系统的灵活性和扩展性。

#include <iostream>
using namespace std;// 抽象产品类
class Product {
public:virtual void operation() = 0;
};// 具体产品类A
class ProductA : public Product {
public:void operation() {cout << "ProductA operation" << endl;}
};// 具体产品类B
class ProductB : public Product {
public:void operation() {cout << "ProductB operation" << endl;}
};// 工厂类
class Factory {
public:Product* createProduct(char type) {switch(type) {case 'A':return new ProductA();case 'B':return new ProductB();default:return nullptr;}}
};int main() {Factory factory;Product* productA = factory.createProduct('A');productA->operation(); // 输出: ProductA operationProduct* productB = factory.createProduct('B');productB->operation(); // 输出: ProductB operationdelete productA;delete productB;return 0;
}

3.工厂模式

工厂方法模式定义:在工厂模式中,工厂父类负责定义创建产品对象的公告接口,而工厂子类负责生成具体的产品对象目的是将产品的实例化操作延迟到工厂子类中完成,通过工厂子类来确定究竟应该实例化哪一个具体产品类。

优点:系统的扩展性好,符合“开闭原则”  。系统加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品即可。

缺点:在添加新产品时,需要编写新的具体产品类,而且要提供与之对应的具体工厂类,系统中类的个数将成对增加,一定程度上增加了系统的复杂度

#include <iostream>// 抽象产品类
class Product {
public:virtual ~Product() {}virtual void operation() = 0;
};// 具体产品类A
class ConcreteProductA : public Product {
public:void operation() override {std::cout << "I'm A" << std::endl;}
};// 具体产品类B
class ConcreteProductB : public Product {
public:void operation() override {std::cout << "I'm B" << std::endl;}
};// 抽象工厂类
class Factory {
public:virtual ~Factory() {}virtual Product* createProduct() = 0;
};// 具体工厂类A
class ConcreteFactoryA : public Factory {
public:Product* createProduct() override {return new ConcreteProductA();}
};// 具体工厂类B
class ConcreteFactoryB : public Factory {
public:Product* createProduct() override {return new ConcreteProductB();}
};int main() {// 使用具体工厂A创建产品AFactory* factoryA = new ConcreteFactoryA();Product* productA = factoryA->createProduct();productA->operation();// 使用具体工厂B创建产品BFactory* factoryB = new ConcreteFactoryB();Product* productB = factoryB->createProduct();productB->operation();delete factoryA;delete factoryB;delete productA;delete productB;return 0;
}

三、设计模式的作用

1. 代码重用性 (即:相同功能的代码,不用多次编写)

2. 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)

3. 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)

4. 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)

5. 使程序呈现高内聚,低耦合的特性

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/622909.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#调用WinAPI部分命令

C#是针对WIndows而生的&#xff0c;开发WIndows应用最快。然而想要让自己程序进阶&#xff0c;就不需深入Windows底层&#xff0c;WinAPI为我们提供了一把利刃。 目录 1、查找窗口句柄 2、查找窗口内子对象 3、指定窗口样式 4、指定窗口扩展样式 5、调整窗口大小Z轴&…

C++在结构(Struct)中使用队列(Queue)

1.结构中实现队列功能: //队列数据 struct QueueData{int data;//数据QueueData *next;//下一个数据 };//队列结构 struct QueueStruct{QueueData *front;//队头,出队用QueueData *end;//队尾,入队用//构造队列QueueStruct(){front=end=NULL;}//空队bool Empty(){return fro…

python-模块-hashlib

1、hashlib模块介绍 Python hashlib 模块主要用于进行哈希&#xff08;hash&#xff09;操作。哈希&#xff08;Hash&#xff09;是一种将任意长度的输入数据映射为固定长度输出数据的算法。hashlib 模块提供了常见的哈希算法的实现&#xff0c;如 MD5、SHA-1、SHA-256 等。 …

还在因为版本不一致重装node吗,用它试试

一、卸载nodejs 首先卸载已安装的nodejs&#xff0c;总体分三步 1)打开控制面板&#xff0c;卸载nodejs 2)打开计算机->高级->环境变量&#xff0c;删除path中nodejs相关的配置 3)打开nodejs安装目录&#xff0c;整体删除 打开cmd&#xff0c;输入以下命令&#xff…

为什么要进行漏洞扫描工作

随着互联网的普及和信息技术的飞速发展&#xff0c;网络安全问题愈发引人关注。其中&#xff0c;漏洞扫描作为保障网络安全的重要手段&#xff0c;受到了广泛的关注和应用。本文将详细介绍漏洞扫描的概念、效果、使用场景等&#xff0c;以期为读者提供有关漏洞扫描的全面了解。…

网络分流规则

现在的网络是越来越复杂。 有必要进行分流。 有一些geosite.dat是已经整理好的&#xff0c;包含许多的网站的分类&#xff1a; 分流规则&#xff1a; route规则 主要是: {"type": "field","outboundTag": "direct","domain&quo…

【Vue3】2-11 : 生命周期钩子函数及原理分析

本书目录&#xff1a;点击进入 一、组件生命周期概述 1.1 官方生命周期 1.2 钩子函数&#xff08;回调函数&#xff09; ▶ 生命周期可划分为三个部分(- >表示执行循序)&#xff1a; 二、实战&#xff1a;测试生命周期流程 &#xff1e; 代码 &#xff1e; 效果 一…

ZZULIOJ 1111: 多个整数的逆序输出(函数专题)

题目描述 输入n和n个整数&#xff0c;以与输入顺序相反的顺序输出这n个整数。要求不使用数组&#xff0c;而使用递归函数实现。 递归函数实现过程如下&#xff1a; void inverse(int n) { 读入一个整数&#xff0c;存入num; if(n >1) { 将后面的n-1个数逆…

代码随想录算法训练营第二十一天|450.删除二叉搜索树中的节点

450.删除二叉搜索树中的节点 private void swapValue(TreeNode a, TreeNode b) {int t a.val;a.val b.val;b.val t; }public TreeNode deleteNode(TreeNode root, int key) {if (root null) {return null;}if (key < root.val) {root.left deleteNode(root.left, key)…

在CMake中自定义宏 add_definitions(-DDEBUG)

hehedalinux:~/Linux/loveDBTeacher-v6$ tree . ├── CMakeLists.txt └── test.c0 directories, 2 files hehedalinux:~/Linux/loveDBTeacher-v6$ test.c #include <stdio.h> #define NUMBER 3int main() {int a 10; #ifdef DEBUGprintf("我是一个程序猿,我…

QT获取程序编译时间与当前时间的区别及应用场景

一.获取编译时间与当前时间的区别 1.编译日期时间&#xff1a;这个信息通常用于标识某个源代码文件或整个应用程序的编译时间&#xff0c;程序一旦编译出来不会再改变&#xff0c;通常用于记录或跟踪代码的版本和更改历史。 2.运行当前日期时间&#xff1a;这是指程序在运行时…

UG装配-多运动组合动画与自动创建装配路径

当圆盘在装配过程中既有旋转运动&#xff0c;又有直线运动的时候&#xff0c;我们需要用到序列中的抽取路径 抽取路径命令在如下位置&#xff0c;需要注意的是&#xff0c;使用抽取路径前&#xff0c;如果有其他零件与所取对象配合&#xff0c;需要先物体脱离或使用拆卸对其脱离…

文件系统详解以及修复

虚拟磁盘分为: kvm : vda(虚拟化磁盘) openstack底层虚拟化是kvm 现在的红帽底层也是用的kvm(因为被红帽收购了) xen : xvda cloustack(被openstack淘汰了)底层虚拟化是xen 已经被kvm淘汰 块设备 IDE设备 : /dev/hda,/dev/hdb SATA/SAS/USB设备 : …

File chooser dialog can only be shown with a user activation.

使用vue开发时&#xff0c;通过ref通过“this.refs.[name].$el.click()”触发按钮时提示“File chooser dialog can only be shown with a user activation.”&#xff0c;按钮不能触发&#xff0c;网上解决办法是“dispatchEvent(new MouseEvent(click))”代替“$el.click()”…

[NAND Flash 6.1] 怎么看时序图 | 从时序理解嵌入式 NAND Read 源码实现

依公知及经验整理,原创保护,禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 前言 每次看到NAND 说明书都脑袋大, 时序图看了脑壳就疼。时序图怎么看呢? 本文就和大家一起学习下。 前置知识基础 在读本文之前,你需要至少了解 你手上…

【STM32F103】继电器的用法

前言 因为最近用到了继电器&#xff0c;所以稍微记录一下继电器的用法。 如果我们需要点亮一个LED灯的时候&#xff0c;我们只需要用单片机的GPIO口去输出高低电平驱动就行。 但是我们需要通过单片机去控制大电压器件的开关的时候&#xff0c;我们就不能简简单单地用一个GPI…

linux Tcp总结

Tcp连接建立时的影响因素 在Client发出SYN后&#xff0c;如果过了1秒 &#xff0c;还没有收到Server的响应&#xff0c;那么就会进行第一次重传&#xff1b;如果经过2s的时间还没有收到Server的响应&#xff0c;就会进行第二次重传&#xff1b;一直重传tcp_syn_retries次。 对…

策略模式-实践

俗话说&#xff1a;条条大路通罗马。在很多情况下&#xff0c;实现某个目标的途径不止一条&#xff0c;例如我们在外出 旅游时可以选择多种不同的出行方式&#xff0c;如骑自行车、坐汽车、坐火车或者坐飞机&#xff0c;可根据实 际情况&#xff08;目的地、旅游预算、旅游时间…

星河璀璨:飞桨星河文心SDK与Gurdance珠联璧合让大模型更像人!

星河璀璨&#xff1a;飞桨星河文心SDK与Gurdance珠联璧合让大模型更像人&#xff01; 本项目效果惊艳&#xff0c;可以在AIStudio星河社区一键运行 只需要在cpu基础版下运行&#xff0c;妈妈再也不怕我的算力不够了&#xff01; 1、引言 2023年8月22日&#xff0c;百度董事…

3.14.1 函数对象 Page139~141

28行&#xff0c;doggie是一个类型为Dog的对象&#xff0c;而“()”是它的一个成员函数&#xff0c;doggie()实际上是doggie.operator() "operator()"既然是函数&#xff0c;那就可以有参数&#xff0c;也可以有返回值 运行结果为&#xff1a;