当前位置: 首页 > news >正文

设计原则和设计模式

目录

一、设计原则:设计模式的指导思想

二、从原则到模式:模板方法的演变过程

2.1 问题场景:动物园表演的 "重复与变化"

2.2 第一步:提取不变骨架(符合单一职责)

2.3 第二步:用抽象约束变化(符合依赖倒置)

2.4 第三步:固定流程顺序(符合开闭原则)

三、模板方法类的代码结构

四、关于多态组合以及普通组合

4.1 普通组合(Non-polymorphic Composition)

4.2 多态组合(Polymorphic Composition)


        在程序员设计程序的时候,发现许多应用场景有类似的套路,同时为了代码的健壮性、可移植性等,经过许多人的总结,形成了设计原则。而设计模式是在设计原则的基础上不断迭代,逐渐总结出一套符合设计原则的固定套路。

一、设计原则:设计模式的指导思想

        设计模式的本质是设计原则的组合应用。在面向对象设计中,几个核心原则构成了所有模式的基础:        

(1)开闭原则(Open-Closed Principle)
对扩展开放,对修改关闭。即通过扩展已有代码来适应变化,而非修改原有代码。

(2)依赖倒置原则(Dependency Inversion Principle)
依赖抽象而非具体实现。高层模块不依赖低层模块,两者都依赖抽象。

(3)单一职责原则(Single Responsibility Principle)
一个类只负责一个功能领域的职责,避免因需求变化导致类频繁修改。

(4)里氏替换原则(Liskov Substitution Principle)
子类可以替换父类并保证程序逻辑不变,即子类需遵守父类的行为契约。

        这些原则都很好理解,比如开闭原则强调的是要对用户提供扩展接口,即可以定义子类去继承父类,而子类可以对父类虚函数重写达到扩展的效果。重点在于扩展,而非修改原有代码。

        而里氏替换原则说不仅仅子类可以重写父类的虚函数,而且不能只关心子类自己的逻辑,还要满足父类的条件。

        这些原则看似抽象,但当我们面对 "重复代码多、变化点分散、扩展困难" 的问题时,它们会引导我们找到结构化的解决方案 —— 这就是设计模式的诞生逻辑。

二、从原则到模式:模板方法的演变过程

        这里我们以模板方法类为例:

        模板方法模式(Template Method Pattern)的核心场景是:多个子类实现的流程骨架相同,但部分步骤的具体实现不同。我们通过一个演化案例来理解其与设计原则的关联。

2.1 问题场景:动物园表演的 "重复与变化"

        动物园有多种表演(狮子表演、海豚表演、猴子表演),所有表演的执行流程骨架完全一致

  1. 开场(播放音乐、主持人介绍);
  2. 主要表演(狮子跳火圈、海豚顶球等,具体内容不同);
  3. 观众互动(狮子与观众合影、海豚握手等,具体方式不同);
  4. 结束(谢幕、清理场地)。

        但如果为每个表演单独编写代码,会出现大量重复(开场和结束逻辑完全相同),且新增表演时需重复编写骨架代码(违反开闭原则)。

2.2 第一步:提取不变骨架(符合单一职责)

        将所有表演的不变步骤(开场、结束)抽离到一个公共父类,让变化步骤(主要表演、互动)留待子类实现。此时父类专注于 "流程骨架",子类专注于 "具体步骤",符合单一职责原则。

2.3 第二步:用抽象约束变化(符合依赖倒置)

        父类通过纯虚函数定义变化步骤的接口,子类必须实现这些接口。高层模块(如调度表演的代码)只需依赖父类抽象,无需关心具体表演类型,符合依赖倒置原则。

2.4 第三步:固定流程顺序(符合开闭原则)

        父类提供一个模板方法(Template Method),该方法按固定顺序调用所有步骤(包括不变步骤和抽象步骤)。子类通过重写抽象步骤扩展功能,无需修改模板方法本身,符合开闭原则。

        至此,模板方法模式的核心结构已形成:父类定义流程骨架,子类实现变化步骤

三、模板方法类的代码结构

// 抽象表演类:定义流程骨架(模板方法)
class Performance {
public:// 模板方法:固定表演流程,子类不可修改void run() {open();          // 不变步骤:开场perform();       // 变化步骤:主要表演(子类实现)interact();      // 变化步骤:观众互动(子类实现)close();         // 不变步骤:结束}// 析构函数声明为虚函数,确保子类析构正常调用virtual ~Performance() = default;protected:// 不变步骤:所有表演的开场逻辑相同void open() {std::cout << "=== 表演开始:播放背景音乐,主持人登场 ===" << std::endl;}// 不变步骤:所有表演的结束逻辑相同void close() {std::cout << "=== 表演结束:演员谢幕,工作人员清理场地 ===" << std::endl;}// 变化步骤:声明为纯虚函数,由子类实现具体表演内容virtual void perform() = 0;// 变化步骤:声明为纯虚函数,由子类实现具体互动方式virtual void interact() = 0;
};// 狮子表演:实现具体步骤
class LionPerformance : public Performance {
protected:void perform() override {std::cout << "狮子表演:跳火圈、钻铁笼" << std::endl;}void interact() override {std::cout << "互动环节:观众与狮子合影(保持安全距离)" << std::endl;}
};// 海豚表演:实现具体步骤
class DolphinPerformance : public Performance {
protected:void perform() override {std::cout << "海豚表演:顶球、跃出水面" << std::endl;}void interact() override {std::cout << "互动环节:观众给海豚喂鱼" << std::endl;}
};// 客户端代码:调度表演(依赖抽象,不依赖具体)
void startShow(Performance* performance) {performance->run();  // 调用模板方法,执行固定流程
}

代码解析:

  1. 抽象类Performance

    • 定义模板方法run(),固定调用open()perform()interact()close()的顺序(骨架不变)。
    • open()close()为具体方法(不变步骤),perform()interact()为纯虚函数(变化步骤,由子类实现)。
  2. 子类实现
    LionPerformanceDolphinPerformance分别重写perform()interact(),实现各自的具体逻辑,无需关心流程顺序。

        假设你现在想要设定表演为狮子表演,只需要写一个狮子表演类,继承于Performance类,然后new一个狮子表演类对象A,直接用这个对象A的指针或者引用调用入口函数即可。

        由于run在基类中已经确定了函数执行的顺序和步骤,所以A指针会在这个对象的虚函数表指针中找到重载的虚函数进行调用。

四、关于多态组合以及普通组合

        组合(Composition)是 "has-a" 的关系(A 类包含 B 类的实例),是面向对象中比继承更灵活的复用方式。因为组合相比继承,可以降低代码的耦合度,如。且组合式黑箱复用,只需要调用类的接口即可,而继承则是白箱复用,对于子类而言可以看到父类的实现细节等。根据是否利用多态,组合可分为两类:

4.1 普通组合(Non-polymorphic Composition)

定义:组合的成员是具体类型的实例,编译时确定依赖关系。

class MusicPlayer 
{
private:MP3Player mp3;  // 依赖具体类型MP3Player
public:void play() { mp3.play(); }
};

特点

  • 编译时绑定(早绑定),性能略高,但灵活性差。
  • 若需替换MP3PlayerWavPlayer,必须修改MusicPlayer的代码(违反开闭原则)。

4.2 多态组合(Polymorphic Composition)

定义:组合的成员是抽象基类的指针 / 引用,运行时可指向任意派生类实例,依赖关系在运行时确定。

class Player {  // 抽象基类
public:virtual void play() = 0;virtual ~Player() = default;
};class MP3Player : public Player {
public:void play() override { /* 播放MP3 */ }
};class WavPlayer : public Player {
public:void play() override { /* 播放Wav */ }
};class MusicPlayer {
private:Player* player;  // 依赖抽象,运行时可指向MP3/WavPlayer
public:MusicPlayer(Player* p) : player(p) {}void play() { player->play(); }
};

特点

  • 运行时绑定(晚绑定),灵活性高,支持动态替换组件(符合开闭原则)。
  • 高层模块(MusicPlayer)依赖抽象(Player),符合依赖倒置原则。

http://www.lryc.cn/news/609006.html

相关文章:

  • 图像、视频、音频多模态大模型中长上下文token压缩方法综述
  • 【Leetcode】2106. 摘水果
  • 【openlayers框架学习】九:openlayers中的交互类(select和draw)
  • 安卓调javaScript Not find method “forceLogout“ implementatidsignature or namesp
  • 【C语言符号单词搜索首位置及数量】2022-10-4
  • web前端React和Vue框架与库安全实践
  • 数组和指针的关系
  • 【LeetCode刷题指南】--二叉树的后序遍历,二叉树遍历
  • VUE父级路由没有内容的解决方案
  • Python自动化测试框架:Unittest 断言
  • 数据结构中使用到的C语言
  • elk快速部署、集成、调优
  • [硬件电路-143]:模拟电路 - 开关电源与线性稳压电源的详细比较
  • mybatis-plus从入门到入土(四):持久层接口之BaseMapper和选装件
  • MySQL极简安装挑战
  • nmon使用教程
  • sqli-labs:Less-23关卡详细解析
  • 基于Python实现生产者—消费者分布式消息队列:构建高可用异步通信系统
  • cpy相关函数区分
  • Ollama模型库模型下载慢完美解决(全平台)
  • 设计模式 - 组合模式:用树形结构处理对象之间的复杂关系
  • 新手向:Python制作贪吃蛇游戏(Pygame)
  • FLUX.1 Krea - 告别“AI味”,感受超自然细节,黑森林最新开源文生图模型 支持50系显卡 一键整合包下载
  • 控制建模matlab练习08:根轨迹
  • js--2048小游戏
  • 【openlayers框架学习】十:openlayers中控件的使用
  • Ubuntu系统VScode实现opencv(c++)视频的处理与保存
  • C语言与数据结构:从基础到实战
  • 解决飞书文档中PDF文档禁止下载的问题
  • Linux 环境下 Docker 安装与简单使用指南