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

整洁架构之道笔记

《Cleam Atchitecture》的学习笔记,不定时更新

文章目录

  • 第二部分 范式概述
    • 结构化编程
    • 面向对象编程
    • 函数式编程
    • 软件架构关注重点
  • 第三部分 设计原则(SOLID)
    • SRP:单一职责原则
      • 反例1:意外的复用
      • 反例2:代码合并
      • 解决方案
    • OCP:开闭原则
    • DIP:依赖反转原则


第二部分 范式概述

结构化编程

主张使用子程序块、程序码区块、if/else和for/while等结构取代goto。现代语言和编程已基本禁止了无限制的goto使用,现在的编程范式默认符合结构化编程要求。

结构化编程对程序控制权的直接转移进行了限制和规范。

面向对象编程

本质是建立一种模型以抽象过程和方法。
这里引用原文介绍下类对象的原理:

“函数调用堆栈可以移动到堆内存区域里,这样函数定义的本地变量就可以在函数返回后继续存在。这个函数就成了一个类的构造函数,而它所定义的本地变量就是类的成员变量,构造函数定义的嵌套函数就成了成员方法。这样依赖就可以利用多态来限制用户对函数指针的使用。”

普通函数中的局部变量存储在栈内存上,函数调用结束后会随着整个栈帧被销毁。为了让函数内部的变量返回后继续存在,可以将其定义在栈上,通过指针手动管理它的生命周期。
除了保存数据,如何进一步保存“行为”?这就是类对象的作用。
暂不引入C++类的写法,可有以下代码实现目的:

// 1. 定义一个结构体,用来保存我们的“捕获变量”和“成员方法”
struct MyClosure {int captured_value; // “捕获”的变量,相当于成员变量void (*print_value)(MyClosure*); // 函数指针,相当于成员方法
};// 2. 定义一个函数,它相当于“构造函数”
MyClosure* CreateMyClosure(int initial_value) {// 在堆上创建结构体实例MyClosure* closure = new MyClosure;// 将“本地变量”的值“捕获”到堆上的结构体中closure->captured_value = initial_value;// 定义一个“嵌套函数”的行为(这里在外部定义,然后赋值给函数指针)// 这个函数可以访问结构体里的 captured_valueclosure->print_value = [](MyClosure* self) {std::cout << "My value is: " << self->captured_value << std::endl;};return closure; // 返回这个“对象”
}int main() {// 3. 通过这个“构造函数”创建一个“对象”MyClosure* my_obj = CreateMyClosure(100);// 这个对象my_obj管理一个在堆上的结构体MyClosure,其由captured_value变量和*print_value函数指针组成// 4. 调用它的“成员方法”my_obj->print_value(my_obj); // 输出:My value is: 100// 5. 修改成员变量my_obj->captured_value = 200;my_obj->print_value(my_obj); // 输出:My value is: 200delete my_obj; // 记得清理return 0;
}

最终目的:上面的例子中用户必须直接调用my_obj->print_value(my_obj),需要传入my_obj本身作为参数,这很麻烦且不安全。面向对象的核心之一是封装:对象自己管理内部状态,用户通过一个清晰的接口与对象交互。
现在改成类的写法,并利用多态实现接口类,给用户提供一个干净且受限的接口。

// 1. 定义一个公共接口(抽象基类)
// 用户只能看到这个接口,不知道背后的实现细节
class IPrinter {
public:virtual void print() const = 0; // 纯虚函数,提供给用户的方法virtual ~IPrinter() = default; // 虚析构确保调用派生类析构
};// 2. 定义一个私有实现类,它“捕获”了之前提到的变量
class SecretPrinter : public IPrinter {
private:int captured_value; // 这就是从“函数局部变量”变成的“成员变量”public:SecretPrinter(int value) : captured_value(value) {} // 这才是真正的构造函数// 这就是由“嵌套函数”变成的“成员方法”void print() const override {std::cout << "My secret value is: " << captured_value << std::endl;}
};// 3. 原来的“函数”现在变成了一个“工厂函数”,返回接口指针
// 它对用户隐藏了 `SecretPrinter` 的具体实现
std::unique_ptr<IPrinter> CreatePrinter(int initial_value) {// 在堆上创建具体实现对象(派生类),但返回其接口指针(基类)return std::make_unique<SecretPrinter>(initial_value);
}int main() {// 4. 用户代码auto my_printer = CreatePrinter(300);my_printer->print(); // 输出:My secret value is: 300// my_printer->captured_value = 400; // 错误!IPrinter 接口没有这个成员return 0;// unique_ptr 自动释放内存,无需手动 delete
}

将变量和方法封装成类后,再通过多态返回基类指针,用户只能使用基类接口中定义的方法,对于派生类,用户无法直接访问或修改其成员变量,无法直接调用其他非虚函数。这就“限制了对函数指针(或更广泛地说,对实现细节)的使用”。
这种方式满足依赖反转原则,就像给一个工具装一个更安全、通用的把儿。
这使得软件架构师可以控制源代码依赖关系,不再受到系统控制流的限制。

面向对象编程对程序控制权的间接转移进行了限制和规范。

函数式编程

也算是结构化编程的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用,变量初始化后不再进行直接赋值,。
即增删改查没有了删和改,这可以避免可变量的并发问题,例如git等版本管理工具就是采用该工作方式,只记录初值和事务记录,显然这对空间的需求更大。
软件架构师应该着力于将大部分处理逻辑归于不可变组件中,可变状态组件的逻辑应该越少越好。

函数式编程对程序中的赋值进行了限制和规范。

软件架构关注重点

上述三种编程范式分别对应了软件架构的三大关注重点:功能性、组件独立性、数据管理。

第三部分 设计原则(SOLID)

SRP:单一职责原则

该原则并非指一个模块或函数应该只做一件事,当然这个也是需要遵守的。
SRP更准确的描述是:一个模块应该对且只对一类用户(行为主体)负责。
即一个模块应该被且只被一类用户(行为主体)使用。
模块是一组内聚的函数和数据的集合,通常来说就是源文件。
内聚是指将对某类行为主体负责的代码绑定在一起。
通过反例可以更好地理解该原则:

反例1:意外的复用

某个薪资管理系统中的Employee类有三个方法:calculatePay()、reportHours()和save(),它们分别被三个完全不同的用户:财务部、人力部、数据库使用,其对应需求也就由这三个部门指定,但却被开发人员放进了一个类里。
calculatePay()和reportHours()使用了相同的逻辑计算工时,且开发人员为了复用代码将其提炼成regularHours(),即两个方法调用了同一个算法。
现在假设财务部要修改工时的计算方法,而人力部并不需要,如果接到任务的开发人员没有注意到算法被复用而修改了代码,一样会通过财务部的测试验证,然而上线后就会造成严重后果。

反例2:代码合并

数据库团队和人力部都需要对Employee的结构进行修改,可能会出现两个来自不同团队的开发人员签出Employee类进行修改,最后代码合并时容易出现难以解决的风险。

总之就是由于一个模块同时包含了多种行为,对多种行为主体负责时,会因为不同行为主体的需求不同而产生风险。

解决方案

显然最明显的方式就是将不同的职责封装到不同的类或模块中。
例如本例将数据和函数分离,拆成三个互不可见的类,共享一个没有方法的数据类:EmployeeData,然后可以再用一个外观类把三个类封装在一起便于实例化和追踪,或者直接让EmployeeData类也由外观类的依赖。
如果不用外观模式进一步封装会不便于管理子系统。

OCP:开闭原则

对扩展开放、对修改封闭。换句话说就是,软件可在无须修改的前提下进行扩展。
实现方式是通过将系统划分为一系列组件,并且将这些组件之间的依赖关系按层次结构进行组织,使得高阶组件不会因低阶组件被修改而受到影响,将程序划分成类并分离到组件中。

越核心的类越应该受到保护,减少受到外部类的影响。而通常包含具体业务规则的类是最核心的,让其不依赖其他类,从而不会因为外围问题导致经常修改。
面向对象编程口可以通过接口(抽象类)反转依赖关系,使得开发人员可以控制源代码依赖关系,不再受到系统控制流的限制。

DIP:依赖反转原则

让实现依赖接口,而非接口依赖实现。
具体的实现类应该参照一个抽象接口来写的,并保证实现了接口中定义的所有方法。而接口是抽象的、稳定的蓝图,它不应该因为某个具体实现类的改变而改变。
高层模块和低层模块都应该依赖于抽象(接口),而不是具体细节。通过引入一个抽象层,来解耦调用方和被调用方的依赖关系。
例如电器和插座,电器是具体实现,插座是统一接口,


文章转载自:

http://iZaiZY6M.pwdmz.cn
http://Rg2zNxlW.pwdmz.cn
http://PCZCOMQW.pwdmz.cn
http://al0yyDYu.pwdmz.cn
http://IcZykSvE.pwdmz.cn
http://YLaFWIAL.pwdmz.cn
http://JWY6L5pI.pwdmz.cn
http://GC3wnbnA.pwdmz.cn
http://HlYFpBRc.pwdmz.cn
http://WgDpFm6s.pwdmz.cn
http://ZPKRcNDE.pwdmz.cn
http://5YighrMe.pwdmz.cn
http://MBLUhBTI.pwdmz.cn
http://8wCVLiqr.pwdmz.cn
http://sgIK2FK3.pwdmz.cn
http://JsTuRWin.pwdmz.cn
http://be6ycWw3.pwdmz.cn
http://ginHcxIW.pwdmz.cn
http://HXHTkVlw.pwdmz.cn
http://juVz3KOn.pwdmz.cn
http://msoH9l6K.pwdmz.cn
http://VmzE6pp6.pwdmz.cn
http://VxgWICO7.pwdmz.cn
http://Tpl4iAX7.pwdmz.cn
http://koq2c4BT.pwdmz.cn
http://glqzCW9V.pwdmz.cn
http://fkcTfG3Z.pwdmz.cn
http://sC9CQ0PO.pwdmz.cn
http://hZQNKpaa.pwdmz.cn
http://Z2sAtRko.pwdmz.cn
http://www.dtcms.com/a/385839.html

相关文章:

  • 深度学习预知识
  • 学习日记-JS+DOM-day56-9.16
  • 51单片机LED闪烁编程实战
  • 字符数组与字符串
  • ⸢ 肆-Ⅱ⸥ ⤳ 风险发现体系的演进(上):背景与现状
  • [js解密分析]方仔照相馆:用3D电子说明书重塑定制积木体验
  • 【Vue3 ✨】Vue3 入门之旅 · 第一篇:Vue3 简介与新特性概览
  • docker 容器中导出pg数据库
  • 【软考】笔记总结一
  • 云望无人机图传16公里原理:云端成像的新纪元,远距离传输不再难
  • OpenHarmony包管理子系统核心源码深度解读:从BundleManager到AMS,彻底打通应用安装、卸载与沙箱机制全链路
  • 10套政务类BI可视化大屏案例:原型设计思路拆解
  • 从零开始的云计算生活——第六十四天,志存高远,性能优化模块
  • 从C++开始的编程生活(10)——string类基本语法和auto自动推导类型
  • 深入理解MySQL主从架构中的Seconds_Behind_Master指标:并行复制优化与云原生实践
  • LAS点云格式转3DTiles全攻略:GISBox的高效实现与技术解析
  • AWS网站访问慢?CloudFront CDN加速配置教程 (2025)
  • AWS Certified AI Practitioner
  • Thomson Reuters 如何通过 AWS转型推动NET现代化
  • TDengine IDMP 基本功能——数据可视化(1. 趋势图)
  • 改进后的 Highcharts for React:更直观、更现代、更高效!
  • 运维安全05,iptables规则保存与恢复
  • 数据可视化 | 热力图理论与案例分析
  • 游戏开发公司应该要注意哪些网络安全问题
  • python 自动化从入门到实战-开发一个接口get post管理请求工具(9)
  • 认知语义学中的意象图式对AI自然语言处理中隐喻分析的影响与启示
  • Edge浏览器的自动化点击系统
  • 达梦数据库巡检常用语句
  • 基于Spring Cloud Gateway的全链路限流策略对比与实践指南
  • ​Oracle存储的实现:一个8KB块能存储多少行数据?​​一个块存不下一行数据会出现什么情况?