C++ 设计模式 - 每日持续更新中

设计模式的核心 - 隔离程序的变化点和稳定点

零:面向对象设计八大原则

①:依赖倒置(Dependency Inversion Principle)

  1. 高层模块不应依赖于低层模块,二者都应依赖于抽象:这意味着程序的高层逻辑不应该直接依赖于具体实现,而是应该依赖于抽象(如接口或抽象类)。

  2. 抽象不应依赖于细节,细节应依赖于抽象:这个原则强调了抽象的独立性和重要性,具体实现应该围绕抽象进行,而不是相反。

通过遵循DIP,可以减少模块之间的耦合,提高代码的可重用性和可维护性,促进更灵活的系统架构。

②:开放封闭原则”(Open/Closed Principle, OCP)

  1. 软件实体(类、模块、函数等)应该对扩展开放,对修改封闭:这意味着你应该能够通过增加新代码来扩展现有的功能,而不是修改已有的代码。通过这样做,可以避免引入错误,并减少对现有功能的影响。

  2. 通过抽象和继承来实现扩展:开放封闭原则通常通过使用接口或抽象类来实现。通过定义通用的接口或基类,可以在不修改原有代码的情况下,创建新的实现类。

  3. 示例:比如,在一个绘图程序中,假设有一个 Shape 接口和多个实现类如 CircleSquare。如果需要添加一个新的形状 Triangle,可以通过创建 Triangle 类来实现,而无需修改 Shape 接口或其他已存在的类。这种方式不仅符合开放封闭原则,还能提高代码的清晰度和可维护性。

实践中的好处

  • 减少缺陷:由于不需要修改现有代码,因此可以降低引入错误的风险。
  • 提高可维护性:新功能可以独立于现有代码进行开发和测试。
  • 促进重用:由于遵循抽象原则,可以更容易地重用现有的模块。

③:单一职责原则(Single Responsibility Principle, SRP)

单一职责原则的核心内容:

  1. 每个类应有且仅有一个原因引起变化:这意味着一个类应该只承担一个职责或功能。如果一个类承担了多个职责,当其中一个职责发生变化时,可能会影响到其他职责,导致代码的耦合性增加和维护难度加大。

  2. 减少耦合:将不同的功能分开,可以降低模块之间的依赖关系,使得每个模块都能独立开发和测试。

  3. 提高可维护性:遵循单一职责原则的代码更易于理解和维护。如果一个类只处理单一任务,开发者可以更快地理解其功能,并在需要修改或扩展时,更少地影响其他部分。

实践中的好处

  • 提高代码的可读性:每个类或模块的功能清晰明了,易于理解。
  • 简化测试:单一职责的类更易于单元测试,因为每个类都只包含特定的功能。
  • 增强灵活性:在添加新功能时,可以更方便地引入新类,而不需要修改现有的类。

④:里氏替换原则(Liskov Substitution Principle, LSP)

里氏替换原则的关键点:

  1. 子类应该可以替代父类:任何可以使用父类的地方,都应该能够使用其子类,而不会改变程序的预期行为。这要求子类在行为上要与父类一致。

  2. 方法的预期行为:子类应该遵循父类的约定,包括输入输出、异常处理等。子类在实现父类的方法时,应该保持父类方法的语义,不能改变方法的预期效果。

  3. 不应改变父类的行为:子类不能对父类的功能进行不必要的修改,比如改变方法的返回值类型或抛出不同的异常。

实践中的好处

  • 增强代码的可重用性:遵循 LSP 可以使子类和父类之间的关系更加清晰,从而提高代码的可重用性。
  • 提高系统的灵活性和可扩展性:当新功能需要添加时,可以通过添加新的子类来实现,而不需要修改已有的代码。
  • 降低错误风险:确保子类能够正确替代父类,降低了因不当替换而导致的错误风险。

⑤:接口隔离原则(Interface Segregation Principle, ISP)

接口隔离原则的关键点:

  1. 细化接口:将大接口分解成多个小接口,使每个接口只包含客户端所需的方法。这有助于减少客户端对不必要功能的依赖。

  2. 降低耦合:通过引入多个小接口,可以降低模块之间的耦合度,使得系统的各个部分更加独立,有助于提升系统的灵活性和可维护性。

  3. 提高可替换性:小接口的实现可以更容易被替换或修改,因为客户端只依赖于自己所需的接口,不会影响到其他模块。

实践中的好处

  • 增强灵活性:通过接口隔离,系统中的每个部分可以独立修改和扩展,而不影响其他部分。
  • 提高可维护性:每个接口都只包含必要的方法,使得代码更易于理解和维护。
  • 减少不必要的依赖:客户端只需依赖于自己关心的接口,从而降低了不必要的耦合。

⑥:优先使用对象组合而不是类继承(Favor Composition Over Inheritance)

关键概念

  1. 对象组合:通过将不同的对象组合在一起以实现复杂的功能。每个对象可以独立地负责其特定的功能,多个对象可以协作完成更复杂的任务。

  2. 类继承:通过继承父类来获得其属性和方法。子类会直接获得父类的所有功能,同时也可能会因为父类的变化而受到影响。

为什么选择组合?

  1. 减少耦合:组合允许对象之间的关系更加灵活,改变或替换一个对象通常不会影响其他对象。相比之下,继承会导致较强的耦合关系,父类的变化可能会影响所有子类。

  2. 提高灵活性:组合可以在运行时动态地改变对象的行为,而继承在编译时就已经确定,灵活性较低。通过组合,系统可以在不改变现有代码的情况下添加新功能。

  3. 避免多重继承的问题:许多编程语言(如 Java 和 C#)不支持多重继承,而组合可以轻松实现类似的效果。通过组合不同的对象,可以获得多种功能而不产生复杂的继承层次结构。

  4. 增强可维护性:通过组合,代码变得更易于理解和维护。每个对象的职责明确,便于管理和修改。

实践中的好处

  • 模块化设计:组合使得系统可以由小模块构建,便于重用和测试。
  • 灵活性和扩展性:在需求变化时,可以通过增加新组件而不是修改现有类来快速响应变化。
  • 清晰的责任分离:组合能够更好地体现单一责任原则(Single Responsibility Principle),使得每个对象都聚焦于自己特定的功能。

⑦:封装变化点(Encapsulate What Varies)

关键概念

  1. 变化点的识别:在设计软件时,需要识别出那些会随时间、需求或技术演进而变化的部分。这些变化点可以是算法、策略、配置、外部服务或数据格式等。

  2. 封装的方式:通过使用接口、抽象类、策略模式、工厂模式等设计模式来封装变化点。这些技术允许在不影响系统其他部分的情况下替换或修改变化点的实现。

  3. 降低耦合:将变化点与系统其他部分分开,从而降低各部分之间的耦合度,使得系统更加灵活和易于修改。

示例

假设我们正在设计一个处理订单的系统,系统需要根据不同的支付方式(如信用卡、PayPal、银行转账等)处理支付。如果直接在订单类中实现支付逻辑,未来添加新的支付方式时就会导致订单类变得复杂并且难以维护。

不封装变化点的设计

class Order {
public:
    void processPayment(const std::string& paymentType) {
        if (paymentType == "CreditCard") {
            // 处理信用卡支付
            handleCreditCardPayment();
        } else if (paymentType == "PayPal") {
            // 处理 PayPal 支付
            handlePayPalPayment();
        } else if (paymentType == "BankTransfer") {
            // 处理银行转账
            handleBankTransferPayment();
        } else {
            std::cout << "Unsupported payment type." << std::endl;
        }
    }
};

在这个设计中,Order 类对支付方式的变化非常敏感。如果要添加新的支付方式,必须修改 Order 类的代码,这样会增加出错的机会并且降低代码的可维护性。

封装变化点的设计

通过使用策略模式,我们可以将支付逻辑封装在独立的支付类中:

#include <iostream>
#include <memory>

// 支付策略接口
class PaymentStrategy {
public:
    virtual void pay(double amount) = 0; // 纯虚函数,表示支付
    virtual ~PaymentStrategy() {} // 虚析构函数
};

// 信用卡支付实现
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing credit card payment of $" << amount << std::endl;
    }
};

// PayPal支付实现
class PayPalPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing PayPal payment of $" << amount << std::endl;
    }
};

// 银行转账支付实现
class BankTransferPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing bank transfer payment of $" << amount << std::endl;
    }
};

// 订单类
class Order {
private:
    std::shared_ptr<PaymentStrategy> paymentStrategy; // 使用 shared_ptr 管理支付策略

public:
    // 构造函数接收一个 shared_ptr
    Order(std::shared_ptr<PaymentStrategy> strategy) 
        : paymentStrategy(strategy) {}

    void processPayment(double amount) {
        paymentStrategy->pay(amount); // 使用支付策略进行支付
    }
};

优势

  1. 灵活性:新的支付方式可以通过实现 PaymentStrategy 接口轻松添加,而无需修改 Order 类的代码。

  2. 可维护性:系统的不同部分之间的耦合度降低,使得每个部分可以独立修改和测试。

  3. 可读性:通过将变化的部分抽象出来,代码的结构更加清晰,易于理解。

实践中的应用

封装变化点的原则不仅适用于支付处理的例子,还广泛适用于其他场景,如:

  • 用户界面:不同的UI实现(如Web、移动端)可以通过界面抽象化来处理。
  • 数据存储:将数据存取的具体实现封装到仓储模式中,使得数据源的变化不会影响到业务逻辑。
  • 算法:通过策略模式封装算法实现,使得算法的变化不会影响到其他逻辑。

⑧:针对接口编程,而不是针对实现编程

定义

  1. 针对接口编程(Programming to an Interface):

    • 在代码设计中,程序员依赖于抽象接口(如接口或抽象类)来定义系统的行为。这意味着代码只需要知道如何使用接口,而不关心具体的实现细节。
    • 通过使用接口,程序可以在运行时根据需要选择不同的实现,这种方式提供了更大的灵活性。
  2. 针对实现编程(Programming to an Implementation):

    • 这种方法是指代码直接依赖于具体的类和实现,而不是抽象接口。这种方式会导致代码变得紧耦合,不易于修改和扩展。
    • 如果实现发生变化,可能需要修改依赖于该实现的所有代码,这会增加维护成本。

一:Template method模式

特点:晚绑定:"我来调用你,而不是你来调用我"

模板方法模式主要由以下几个角色组成:

  • 抽象类(Abstract Class)

    • 包含一个或多个模板方法。
    • 在模板方法中定义算法的基本步骤,部分步骤为抽象方法,子类需要实现这些方法。
  • 具体类(Concrete Class)

    • 继承抽象类并实现抽象方法。
    • 具体类可以修改某些步骤的实现,从而实现算法的具体细节。

2. 模式的优点

  • 代码复用:模板方法允许在多个子类中复用相同的算法结构。
  • 控制反转:子类可以选择实现部分算法步骤,从而灵活地变化算法行为。
  • 易于扩展:添加新算法只需创建新的子类,无需修改已有代码。

3. 模式的缺点

  • 灵活性下降:由于算法的步骤是固定的,灵活性可能不如策略模式。
  • 类数增加:每个不同的算法都需要一个新的子类,可能导致类的数量增加。

代码实践 

【设计模式专题之模板方法模式】18-咖啡馆

#include<iostream>
#include<vector>
#include<memory>
using namespace std;
#define endl '\n'
    
class Coffee {
public:
	void Run()
	{
		Name_coffee();
		Grind();
		Brewing();
		Add_condiments();
	}
protected:
	Coffee() {};
	void Grind()
	{
		cout << "Grinding coffee beans" << endl;
	}
	void Brewing()
	{
		cout << "Brewing coffee" << endl;
	}
	virtual void Name_coffee() = 0;
	virtual void Add_condiments() = 0;
	virtual ~Coffee() = default;
};


class Latte : public Coffee {
protected:
	void Name_coffee() override
	{
		cout << "Making Latte:" << endl;
	}
	void Add_condiments() override
	{
		cout << "Adding milk" << endl << "Adding condiments" << endl;
	}
};

class A_coffee : public Coffee {
protected:
	void Name_coffee() override
	{
		cout << "Making American Coffee:" << endl;
	}
	void Add_condiments() override
	{
		cout << "Adding condiments" << endl;
	}
};


signed main() {
    cin.tie(0) -> sync_with_stdio(false);
    int x = 0;
   	while (cin >> x) 
   	{
        shared_ptr<Coffee> coffeePtr; 
        switch (x) 
        {
            case 1:
                coffeePtr = make_shared<A_coffee>();
                break;
            case 2:
                coffeePtr = make_shared<Latte>();
                break;
            default:
                continue; 
        }
        coffeePtr -> Run();
        cout << endl;
    }

    return 0;
}

二:策略模式

 

1:关键组成部分

  1. 策略接口:定义所有支持的算法的公共接口。
  2. 具体策略类:实现策略接口,封装具体的算法。
  3. 上下文(Context):持有一个对策略接口的引用,可以在运行时切换策略。

2:工作原理

  • 上下文类使用策略接口来调用具体的策略算法。
  • 客户端可以根据需要选择并设置具体的策略,策略的实现细节对客户端是透明的。

3:适用场景

  • 当你有多个相关的类,仅仅是行为不同,而可以通过不同的算法实现。
  • 当你需要在运行时选择算法。
  • 当你希望避免使用大量的条件语句(如 if-else 或 switch)。

代码实践: 

【设计模式专题之策略模式】14. 超市打折
分析:该问题中,策略执行往往是稳定的,变化点在于用户策略的选择。

#include<iostream>
#include<algorithm>
#include<vector>
#include<memory>
using namespace std;
#define endl '\n'
const int maxn = 1e6 + 10;
int n, m, k, d, T = 1, A, B;

//支付基类
class Pay {
public:
	Pay() {}
	virtual ~Pay() = default;	
	virtual int cost(int x) const = 0;
};

class Percentage_pay : public Pay {
public:
	int cost(int amount) const override
	{
		return amount * 0.9;
	}
};

class Full_pay : public Pay {
public:
	int cost(int amount) const override
	{
		if(amount >= 300)return amount - 40;
		if(amount >= 200)return amount - 25;
		if(amount >= 150)return amount - 15;
		if(amount >= 100)return amount - 5;
		return amount;
	}
};

//具体策略类
class Shopping {
private:
	std::shared_ptr<Pay> ptr;
public:
	Shopping(std::shared_ptr<Pay> p) : ptr(p) { } 
	void Run(int x) const
	{
		cout << ptr -> cost(x) << endl;
	}
};



signed main() 
{
    cin.tie(0) -> sync_with_stdio(false);
    int T = 0;
    cin >> T;
    std::shared_ptr<Pay> ptr;
    while(T--)
    {
    	cin >> n >> m;
    	if(1 == m)ptr = make_shared<Percentage_pay>();
    	else if(2 == m) ptr = make_shared<Full_pay>();
    	Shopping shop(ptr);
    	shop.Run(n);
    }
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索 SVG 创作新维度:svgwrite 库揭秘

文章目录 **探索 SVG 创作新维度&#xff1a;svgwrite 库揭秘**背景介绍库简介安装指南基础函数使用实战场景常见问题与解决方案总结 探索 SVG 创作新维度&#xff1a;svgwrite 库揭秘 背景介绍 在数字艺术和网页设计领域&#xff0c;SVG&#xff08;Scalable Vector Graphic…

QT:MaintenanceTool 模块安装工具

QT的MaintenanceTool 工具对已安装的 Qt 进行卸载、修复等其他操作时提示At least one valid and enabled repository required for this action to succeed 解决方式&#xff1a;在设置中添加一个临时的仓库 https://mirrors.tuna.tsinghua.edu.cn/qt/online/qtsdkrepositor…

Serv00 免费虚拟主机 零成本搭建 PHP / Node.js 网站

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 Serv00 是一个提供免费虚拟主机的平台&#xff0c;包含了 3GB 的存储空间和 512MB 的内存空间&#xff0c;足够我们搭建一个 1IP 的小网站了。同时他还不限制每月的流量&#xff0c;并提供了 16 个数据库&…

【深度学习代码调试5】标准化数据集:TensorFlow Datasets (TFDS)自动化数据加载与预处理

【标准化数据集】TensorFlow Datasets、TFDS&#xff1a;自动化数据加载与预处理 写在最前面1. 什么是 TensorFlow Datasets (TFDS)?主要特点&#xff1a; 2. TFDS 的核心 API&#xff1a;tfds.builder 和 download_and_preparetfds.builder&#xff1a;创建数据集构建器示例&…

FPGA实现PCIE视频采集转USB3.0输出,基于XDMA+FT601架构,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的PCIE方案本博已有的USB通信方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存FT601功能和硬件电路FT601读时序解读FT601写时序解读U…

【源码+文档】基于JavaWeb的村民健康管理平台【提供源码+答辩PPT+参考文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

未来医疗:大语言模型如何改变临床实践、研究和教育|文献精析·24-10-23

小罗碎碎念 这篇文章探讨了大型语言模型在医学领域的潜在应用和挑战&#xff0c;并讨论了它们在临床实践、医学研究和医学教育中的未来发展。 姓名单位名称&#xff08;中文&#xff09;Jan Clusmann德国德累斯顿工业大学埃尔朗根弗雷斯尼乌斯中心数字化健康研究所Jakob Nikola…

光纤传感器比传统传感器强在哪?——以大坝监测为例

应用介绍 大坝安全监测中心经常对当前工程中的大坝进行检查, 以确保水电站的安全运行。 大坝原有的观测模式是传感器加上人工观测模式&#xff0c;多数传感器经过多年运行后逐渐老化&#xff0c;出现测点损伤&#xff0c;且精度无法与现有光纤传输传感器相比&#xff0c;受现…

Django+Vue全栈开发项目入门(一)

Vue项目搭建过程 1、使用脚手架工具搭建项目 2、准备静态资源 3、调整生成项目结构 使用脚手架工具搭建项目 网络请求库axios Axios是一个基于Promise的HTTP库&#xff0c;适用于浏览器和node.js环境&#xff0c;用于发送网络请求。 特点 跨平台性&#xff1a;Axios既可…

Unity Apple Vision Pro 保姆级开发教程-环境配置、导入 PolySpatial 案例、程序发布到设备

视频教程 Unity 环境配置、导入 PolySpatial 案例、程序发布到设备 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【保姆级】 | Unity 中文课堂 教程说明 这期教程我将介绍使用 Unity 开发 Apple Vision Pro 应用所需要的 Unity 环境配置&…

python实现投影仪自动对焦

这是一款投影仪,它带有对焦摄像头 它是如何自动对焦的呢? 我们先看一下对焦算法展示效果 说明:左侧是原视频,右侧是对调焦后的视频帧展示,如果下一帧视频比当前帧清晰就会显示下一帧,否则,还是显示当前帧,直至找到更清晰的帧 原理说明: 在投影仪上对焦摄像头就会实…

CDP和数据仓库怎么选?

一、CDP 是什么&#xff1f; 1.定义&#xff1a; CDP 全称是Customer Data Platform&#xff08;客户数据平台&#xff09;。是一种营销技术工具&#xff0c;它能够将来自不同渠道和系统&#xff08;如网站、移动应用、客户服务系统、营销自动化平台、社交媒体等&#xff09;…

C#PropertyGrid下拉选择数据报错

1、问题点--PropertyGrid下拉框报错 PropertyGrid&#xff1a;属性窗口&#xff1a;滚轮选择或者手动输入不报错&#xff0c;下拉框选择报错 属性值无效&#xff1a;类型“System:String”的对象无法转化为类型“System:Int32” PropertyGrid&#xff1a;属性窗口&#xff1a;…

论文笔记:SIBO: A Simple Booster for Parameter-Efficient Fine-Tuning

ACL 2024 1 intro 基于 Transformer 的大模型一般都有很多层 在广泛采用的 PEFT 技术&#xff08;包括 Adapters 和 LoRA&#xff09;中&#xff0c;尤其是在深层中&#xff0c;也存在过度平滑现象&#xff08;即token之间的相似度很高&#xff09;论文评估了同一语句中 toke…

【Linux学习】(3)Linux的基本指令操作

前言 配置Xshell登录远程服务器Linux的基本指令——man、cp、mv、alias&which、cat&more&less、head&tail、date、cal、find、grep、zip&tar、bc、unameLinux常用热键 一、配置Xshell登录远程服务器 以前我们登录使用指令&#xff1a; ssh 用户名你的公网…

华为云容器引擎(CCE):赋能企业云原生转型

在当今数字化时代&#xff0c;企业面临着日益复杂的应用部署和管理挑战。为了解决这些问题&#xff0c;容器技术应运而生&#xff0c;成为云原生架构的核心。华为云容器引擎&#xff08;CCE&#xff09;作为一款全面的容器管理解决方案&#xff0c;旨在帮助企业实现高效、灵活的…

Redis 安装部署与常用命令

目录 一、关系数据库与非关系型数据库 1.1 关系型数据库 1.2 非关系型数据库 1.3关系型数据库和非关系型数据库区别 &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 &#xff08;3&#xff09;对事务性的支持不同 1.4 非关系型数据…

腾讯推出ima.copilot智能工作台产品 由混元大模型提供技术支持

腾讯公司近期推出了一款名为ima.copilot&#xff08;简称ima&#xff09;的智能工作台产品&#xff0c;它由腾讯混元大模型提供技术支持。这款产品旨在通过其会思考的知识库&#xff0c;为用户开启搜读写的新体验。ima.copilot的核心功能包括知识获取、打造专属知识库以及智能写…

【算法】递归系列:递归初介绍,练习:231.2 的幂、

目录 一、理解递归 1、什么是递归&#xff1f; 2、为什么会使用递归&#xff1f; 3、递归使用的场景&#xff1f; 4、那么如何写出递归解法&#xff1f; 二、实践 231. 2的幂 1.函数头的设计 2.只关心某一个子问题是如何解决的 ->函数体的书写 3.注意一下递归函数的出…

个人品牌塑造与商业增长的加速器:开源AI智能名片2+1链动模式S2B2C商城小程序源码的应用研究

摘要&#xff1a;本文旨在深入探讨个人品牌塑造与商业增长过程中的核心要素&#xff0c;特别是“开源AI智能名片21链动模式S2B2C商城小程序源码”在提升品牌影响力、吸引潜在客户、促进团队协作及推动商业增长方面的具体应用与显著成效。通过详细分析一位微商从业者的实际案例&…