[C++设计模式] 为什么需要设计模式?

news2024/12/26 20:36:34


文章目录

  • 什么是设计模式?
  • 为什么需要设计模式?
  • GOF 设计模式
  • 再次理解面向对象
  • 软件设计固有的复杂性
  • 软件设计复杂性的根本原因
  • 如何解决复杂性?
      • 分解
      • 抽象
  • 结构化 VS 面向对象(封装)
      • 结构化设计代码示例:
      • 面向对象设计代码示例:
      • 对比总结
  • 软件设计的目标:复用!
  • 总结

在软件开发过程中,开发者经常面临复杂系统的设计与实现。为了应对这一挑战,“设计模式”应运而生。设计模式是一种解决问题的“套路”,它既简化了软件开发的过程,又提高了代码的复用性和可维护性。本文将从设计模式的概念出发,逐步分析软件设计的复杂性以及如何通过面向对象和设计模式来降低复杂性,最终实现高效的代码复用。


什么是设计模式?

“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。

——Christopher Alexander

设计模式(Design Pattern)是对软件开发过程中反复出现的设计问题所提供的通用解决方案。它不是代码,而是经过验证的“最佳实践”,以一种结构化的方式记录了解决问题的思想。

  • 核心理念:通过设计模式,开发者无需重复发明轮子,可以直接借用已有的设计经验。(复用!!!)
  • 分类:设计模式主要分为三大类:
    • 创建型模式:解决对象创建相关的问题,如单例模式(Singleton)、工厂模式(Factory Method)。
    • 结构型模式:关注对象间的组合和结构,如适配器模式(Adapter)、装饰器模式(Decorator)。
    • 行为型模式:处理对象间的职责分配和通信,如观察者模式(Observer)、状态模式(State)。

为什么需要设计模式?

  1. 解决常见问题
    软件开发中,许多问题是重复出现的。设计模式总结了这些问题的通用解决方案,减少了开发中的试错。
  2. 提升代码质量
    使用设计模式可以使代码更加易读、灵活和扩展性强,方便后期维护。
  3. 提高开发效率
    借助设计模式,开发者可以专注于业务逻辑,而不是基础框架设计,从而加快开发进程。

GOF 设计模式

GOF(Gang of Four,四人帮)指的是设计模式的奠基者——Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。这四位作者在 1994 年出版了经典著作 《Design Patterns: Elements of Reusable Object-Oriented Software》,首次系统化地总结了 23 种面向对象设计模式。

  • 核心观点
    1. 软件设计应遵循面向对象的基本原则(如单一职责、开放封闭等)。
    2. 模式之间可以组合使用,实现更强大的功能。

再次理解面向对象

对于C++程序猿来说,需要将底层思维与抽象思维都进行分析。

**底层思维:**向下,如何把握机器底层从微观理解对象构造

• 语言构造

• 编译转换

• 内存模型

• 运行时机制

**抽象思维:**向上,如何将我们的周围世界抽象为程序代码

• 面向对象

• 组件封装

• 设计模式

• 架构模式

良好的设计模式可以根据语言在底层的逻辑和在语言逻辑层面的设计进行相结合,以此来提高代码质量和开发效率。

所以,在讨论设计模式之前,必须再次理解面向对象的核心思想:

  1. 封装:将数据和操作封装到对象中,隐藏内部实现。
  2. 继承:通过继承重用已有的代码,减少重复。
  3. 多态:通过统一的接口实现不同的行为。

面向对象通过以上三大特性为设计模式奠定了基础,是理解设计模式的关键。


软件设计固有的复杂性

建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。

——Object-Oriented Analysis and Design with Applications

复杂性是软件设计中的一大挑战。软件的复杂性源于多个维度:

  1. 规模增长:软件系统的功能越多,模块之间的关系越复杂。
  2. 需求变化:用户需求总是在变化,如何适应这些变化是设计的难题。
  3. 技术多样性:系统可能需要集成多种技术,增加了设计和实现的难度。

软件设计复杂性的根本原因

  1. 模块之间的耦合:模块间的强耦合导致修改一个模块可能会影响其他模块。
  2. 缺乏抽象:没有抽象层,导致系统难以扩展和维护。
  3. 过度复杂的设计:为了应对变化,设计变得过于复杂,反而不利于实现。
  4. 跨平台的支持:多个版本设计。

原因在后期会进行功能的添加或者修改,但是在刚开始程序进行设计的时候的代码逻辑无法使得在基础上直接添加新功能,所以只能在原来的上面进行强行添加,也就是if - else屎山设计的由来~


如何解决复杂性?

分解

人们面对复杂性有一个常见的做法:即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。

抽象

更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化了的对象模型。

  1. 遵循设计原则
    • 单一职责原则(SRP):每个模块只做一件事。
    • 开放封闭原则(OCP):对扩展开放,对修改封闭。
    • 依赖倒置原则(DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
  2. 采用面向对象思想:通过封装、继承和多态降低耦合、增强复用性。
  3. 使用设计模式:通过复用成熟的设计经验,将复杂的设计问题分解为可管理的模块。

结构化 VS 面向对象(封装)

我们以一个现实问题为例:计算矩形的面积和周长

结构化设计代码示例:

在结构化设计中,数据和功能是分离的。函数主要以操作为中心,而数据仅作为参数传递。

#include <iostream>
using namespace std;

// 数据结构:存储矩形的长度和宽度
struct Rectangle {
    double length;
    double width;
};

// 函数:计算面积
double calculateArea(const Rectangle& rect) {
    return rect.length * rect.width;
}

// 函数:计算周长
double calculatePerimeter(const Rectangle& rect) {
    return 2 * (rect.length + rect.width);
}

int main() {
    // 创建一个矩形
    Rectangle rect = {5.0, 3.0};

    // 调用函数进行操作
    cout << "Area: " << calculateArea(rect) << endl;
    cout << "Perimeter: " << calculatePerimeter(rect) << endl;

    return 0;
}

特点

  1. 数据 (Rectangle) 和操作(calculateAreacalculatePerimeter)是独立的。
  2. 如果新增功能(如计算对角线长度),需要添加新的函数,操作逻辑分散。
  3. 数据和逻辑间的耦合较弱**,但扩展性差**。

面向对象设计代码示例:

在面向对象设计中,数据和功能封装在一个类中,操作直接基于对象调用。

#include <iostream>
#include <cmath> // 为了计算对角线
using namespace std;

// 面向对象设计:定义矩形类
class Rectangle {
private:
    double length;
    double width;

public:
    // 构造函数
    Rectangle(double l, double w) : length(l), width(w) {}

    // 方法:计算面积
    double calculateArea() const {
        return length * width;
    }

    // 方法:计算周长
    double calculatePerimeter() const {
        return 2 * (length + width);
    }

    // 方法:计算对角线长度
    double calculateDiagonal() const {
        return sqrt(length * length + width * width);
    }

    // 设置长度和宽度
    void setDimensions(double l, double w) {
        length = l;
        width = w;
    }

    // 显示矩形信息
    void display() const {
        cout << "Rectangle [Length: " << length << ", Width: " << width << "]" << endl;
    }
};

int main() {
    // 创建一个矩形对象
    Rectangle rect(5.0, 3.0);

    // 调用对象方法进行操作
    rect.display();
    cout << "Area: " << rect.calculateArea() << endl;
    cout << "Perimeter: " << rect.calculatePerimeter() << endl;
    cout << "Diagonal: " << rect.calculateDiagonal() << endl;

    return 0;
}

特点

  1. 数据 (length, width) 和操作(如 calculateArea, calculatePerimeter)被封装在类中。
  2. 新增功能(如计算对角线长度)可以通过类方法轻松扩展。
  3. 高度封装,使对象的行为与数据直接相关,提高了模块化和复用性。

对比总结

特点结构化设计面向对象设计
数据与操作的关系数据和操作分离,函数作用于独立的数据数据和操作封装在一个对象内,操作与数据密切相关
扩展性新增功能需要新增函数,整体设计不易扩展新增功能只需添加类方法,设计更具扩展性
维护性操作逻辑分散在多个函数中,难以维护操作封装在类中,逻辑清晰,易于维护
代码复用性数据结构和操作复用性较差类和方法具有较高的复用性
示例中的实现复杂度数据操作较为简单,但难以应对复杂系统初期实现略复杂,但适合复杂系统

通过以上对比,可以直观地看出,结构化设计更适合小型、单一功能的程序,而面向对象设计在解决复杂系统时更具优势。

结构化设计面向对象设计
以功能为中心,数据为次要目标以对象为中心,功能服务于对象
数据和操作分离数据和操作封装在对象内部
难以复用和扩展具有较好的复用性和扩展性

面向对象设计通过关注对象及其交互,解决了结构化设计中模块间强耦合的问题,为设计模式的实施提供了基础。


软件设计的目标:复用!

复用是软件设计的核心目标之一。设计模式通过以下方式实现复用:

  1. 代码复用:减少重复代码,提高开发效率。
  2. 设计复用:使用模式模板应对类似的设计需求。
  3. 经验复用:将设计经验总结为模式,方便传递和共享。

总结

设计模式是应对软件复杂性的重要工具,是面向对象思想的升华。通过学习和实践设计模式,开发者可以设计出灵活、可扩展且易维护的系统。对于初学者来说,了解设计模式的基本分类和使用场景,是深入学习的第一步。


关于具体不同设计模式是如何设计的,请阅读笔者该专栏其他文章。

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

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

相关文章

级联树结构TreeSelect和上级反查

接口返回结构 前端展示格式 前端组件 <template><div ><el-scrollbar height"70vh"><el-tree :data"deptOptions" :props"{ label: label, children: children }" :expand-on-click-node"false":filter-node-me…

Figma入门-自动布局

Figma入门-自动布局 前言 在之前的工作中&#xff0c;大家的原型图都是使用 Axure 制作的&#xff0c;印象中 Figma 一直是个专业设计软件。 最近&#xff0c;很多产品朋友告诉我&#xff0c;很多原型图都开始用Figma制作了&#xff0c;并且很多组件都是内置的&#xff0c;对…

【Unity基础】使用InputSystem实现物体跳跃

要在Unity中使用 InputSystem 实现小球按空格键跳起的效果&#xff0c;可以按照以下步骤进行&#xff1a; 1. 安装 InputSystem 包 首先&#xff0c;确保你已经安装了 Input System 包。你可以通过以下步骤安装&#xff1a; 打开 Unity 编辑器&#xff0c;点击菜单 Window -…

【ArkTS】使用AVRecorder录制音频 --内附录音机开发详细代码

系列文章目录 【ArkTS】关于ForEach的第三个参数键值 【ArkTS】“一篇带你读懂ForEach和LazyForEach” 【小白拓展】 【ArkTS】“一篇带你掌握TaskPool与Worker两种多线程并发方案” 【ArkTS】 一篇带你掌握“语音转文字技术” --内附详细代码 【ArkTS】技能提高–“用户授权”…

一种多功能调试工具设计方案开源

一种多功能调试工具设计方案开源 设计初衷设计方案具体实现HUB芯片采用沁恒微CH339W。TF卡功能网口功能SPI功能IIC功能JTAG功能下行USB接口 安路FPGA烧录器功能Xilinx FPGA烧录器功能Jlink OB功能串口功能RS232串口RS485和RS422串口自适应接口 CAN功能烧录器功能 目前进度后续计…

浏览器的事件循环机制

浏览器和Node的事件循环机制 引言浏览器的事件循环机制 引言 由于JS是单线程的脚本语言&#xff0c;所以在同一时间只能做一件事情&#xff0c;当遇到多个任务时&#xff0c;我们不可能一直等待任务完成&#xff0c;这会造成巨大的资源浪费。为了协调时间&#xff0c;用户交互…

Zabbix添加防火墙温度监控值实战

我们在Zabbix监控系统会监控诸如Server、network device、application等实例&#xff0c;通常我们在监控某个具体产品时&#xff0c;我们会找到具体的监控模板&#xff0c;在设备添加到平台以后&#xff0c;将模板链接到该设备&#xff0c;但很多时候我们企业内部的设备是没有标…

【k8s】创建基于sa的token的kubeconfig

需求 创建一个基于sa的token的kubeconfig文件&#xff0c;并用这个文件来访问集群。 具体创建sa 和sa的token请参考文章: 【k8s】给ServiceAccount 创建关联的 Secrets-CSDN博客 创建sa apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata:namespace: jtkjdevnam…

Fastapi + vue3 自动化测试平台---移动端App自动化篇

概述 好久写文章了&#xff0c;专注于新框架&#xff0c;新UI界面的实践&#xff0c;废话不多说&#xff0c;开搞 技术架构 后端&#xff1a; Fastapi Airtest multiprocessing 前端&#xff1a; 基于 Vue3、Vite、TypeScript、Pinia、Pinia持久化插件、Unocss 和 Elemen…

Apache Doris 现行版本 Docker-Compose 运行教程

特别注意&#xff01;Doris On Docker 部署方式仅限于开发环境或者功能测试环境&#xff0c;不建议生产环境部署&#xff01; 如有生产环境或性能测试集群部署诉求&#xff0c;请使用裸机/虚机部署或K8S Operator部署方案&#xff01; 原文阅读&#xff1a;Apache Doris 现行版…

Docker的彻底删除与重新安装(ubuntu22.04)

Docker的彻底删除与重新安装&#xff08;ubuntu22.04&#xff09; 一、首先我们彻底删除Docker1、删除docker及安装时自动安装的所有包2、删除无用的相关的配置文件3、删除相关插件4、删除docker的相关配置和目录 二、重新安装1、添加 Docker 的官方 GPG 密钥&#xff1a;2、将…

Nginx学习-安装以及基本的使用

一、背景 Nginx是一个很强大的高性能Web和反向代理服务&#xff0c;也是一种轻量级的Web服务器&#xff0c;可以作为独立的服务器部署网站&#xff0c;应用非常广泛&#xff0c;特别是现在前后端分离的情况下。而在开发过程中&#xff0c;我们常常需要在window系统下使用Nginx…

力扣hot100道【贪心算法后续解题方法心得】(三)

力扣hot100道【贪心算法后续解题方法心得】 十四、贪心算法关键解题思路1、买卖股票的最佳时机2、跳跃游戏3、跳跃游戏 | |4、划分字母区间 十五、动态规划什么是动态规划&#xff1f;关键解题思路和步骤1、打家劫舍2、01背包问题3、完全平方式4、零钱兑换5、单词拆分6、最长递…

系统--线程互斥

1、相关背景知识 临界资源多线程、多执行流共享的资源,就叫做临界资源临界区每个线程内部,访问临界资源的代码互斥在任何时刻,保证有且只有一个执行流进入临界区,访问临界资源,对临界资源起到保护作用原子性不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么…

Qt桌面应用开发 第十天(综合项目二 翻金币)

目录 1.主场景搭建 1.1重载绘制事件&#xff0c;绘制背景图和标题图片 1.2设置窗口标题&#xff0c;大小&#xff0c;图片 1.3退出按钮对应关闭窗口&#xff0c;连接信号 2.开始按钮创建 2.1封装MyPushButton类 2.2加载按钮上的图片 3.开始按钮跳跃效果 3.1按钮向上跳…

getchar()

getchar():从计算机终端&#xff08;一般是键盘&#xff09;输入一个字符 1、getchar返回的是字符的ASCII码值&#xff08;整数&#xff09;。 2、getchar在读取结束或者失败的时候&#xff0c;会返回EOF 输入密码并确认&#xff1a; scanf读取\n之前的内容即12345678 回车符…

linux 获取公网流量 tcpdump + python + C++

前言 需求为&#xff0c;统计linux上得上下行公网流量&#xff0c;常规得命令如iftop 、sar、ifstat、nload等只能获取流量得大小&#xff0c;不能区分公私网&#xff0c;所以需要通过抓取网络包并排除私网段才能拿到公网流量。下面提供了一些有效得解决思路&#xff0c;提供了…

Node.js:开发和生产之间的区别

Node.js 中的开发和生产没有区别&#xff0c;即&#xff0c;你无需应用任何特定设置即可使 Node.js 在生产配置中工作。但是&#xff0c;npm 注册表中的一些库会识别使用 NODE_ENV 变量并将其默认为 development 设置。始终在设置了 NODE_ENVproduction 的情况下运行 Node.js。…

KAN-Transfomer——基于新型神经网络KAN的时间序列预测

1.数据集介绍 ETT(电变压器温度)&#xff1a;由两个小时级数据集&#xff08;ETTh&#xff09;和两个 15 分钟级数据集&#xff08;ETTm&#xff09;组成。它们中的每一个都包含 2016 年 7 月至 2018 年 7 月的七种石油和电力变压器的负载特征。 traffic(交通) &#xff1a;描…

中安证件OCR识别技术助力鸿蒙生态:智能化证件识别新体验

在数字化和智能化的浪潮中&#xff0c;伴随国产化战略的深入推进&#xff0c;国产操作系统和软件生态的建设逐渐走向成熟。鸿蒙操作系统&#xff08;HarmonyOS Next&#xff09;作为华为推出的重要操作系统&#xff0c;凭借其开放、灵活和高效的特点&#xff0c;正在加速在多个…