#面向对象设计原则

Quote

你想要一个香蕉,但得到的却是一个大猩猩拿着香蕉,而其还有整个丛林。” — Joe Armstrong(Erlang语言发明人)

架构原则:

命名、排版、文档

编程技巧:资源的独立与共享

#单一职责原则 #接口隔离原则 #依赖倒置原则 #资源获取即初始化 #原子性 #面向切面
(48 封私信 / 80 条消息) 什么是面向切面编程? - 知乎
单一职责原则的思维:为什么你的代码总在“牵一发而动全身” - AI·NET极客圈 - 博客园
(57 封私信 / 80 条消息) 接口隔离原则(Interface Segregation Principle) - 知乎

集成电路,物理上是分隔的,不需要担心另一个元件的信号影响这一个元件的运行。
程序代码,前人栽树,后人乘凉,改代码时容易造成“牵一发而动全身”。

两个解决办法:

独立而完备的原子性代码。真正原子性的不可分割的代码往往只是三四行聚集的“代码颗粒”。所以我们在描述类的时候,要怀有一点“这个类未来可能成为基类”,“这个类未来可能要被拆分”的意识,让后人维护代码更加轻松。

这样你就实现了:

面向切面

所谓“切面”,可以理解为代码块之间耦合度较低的部分。当很多代码都在类似的位置低耦合,就形成了一个切面。

在面向对象的表达方式,
“洗澡”可以包装成一个类,或者最起码是个函数。
“进去洗澡”和“洗完出来”就是耦合度较低的环节
在面向切面的表达方式,
“洗澡”是核心关注点
“进去洗澡”和“洗完出来”是横切关注点

面向切面,就是要我们关注对象与对象之间定义的边界,这个边界越平整越便于维护。

切面,是为了我们“望闻问切”。在切面中看到数据流通的“血管”,也就可以把变量印出来打印“抽血”(在计算机语言随时printf可是可以,但是难以定位和统一控制;在verilog中暴露变量更加困难,面向切面更加有用了)

对于依赖中的“切面”,用抽象接口分隔出上下游的依赖,这就是依赖倒置原则。

编程技巧:状态模型示例

一个任务的状态应该分四个象限

CPU在,RAM在 CPU在,RAM不在
CPU不在,RAM在 CPU不在,RAM不在
“CPU在,RAM不在”显然是不能成立的(无法正常工作)
所以任务分为了
graph TD
    A[运行:CPU在,RAM在] --暂停--> B[暂存:CPU不在,RAM在]
    A --退出--> C[空闲:CPU不在,RAM不在]
    B --继续--> A
    C --进入--> A

进一步还可以有

graph LR
B[暂存:CPU不在,RAM在] --回收--> C[空闲:CPU不在,RAM不在]
C --预备--> B

面对多任务,还可以把暂存的原因分为

编程技巧:C语言实现“面向对象”

基于结构体:虽然C的函数是静态的,无法实例化,但是结构体可以实例化。
基于指针:对象的方法、对象的父类、对象的子类,本质上都可以用指针表示,因为指针就是最灵活的组合。

开工!

在结构体中定义指针指向基类
在函数中传入指针指向对象

部分函数作为工厂方法也就是构造函数返回结构体指针。

这样就实现面向对象了。HAL库也是这样实现面向对象的。下面是一个比较有名的库

lw_oopc(C语言的面向对象) - robert_cai - 博客园
Akagi201/lw_oopc: Light Weight Object Oriented C macros
关于-LW_OOPC学习01_金永华 csdn-CSDN博客

C语言唯一做不到的,是省略对象自身的参数传入(省略self或者this的能力)

有了这些定义,用起来就跟简单c++的类一样了,只是定义一个类的实例时应采用如下一种格式:

Dog* dog = Dog_new();
dog->Init(dog);

调用自身的方法:

Bird* bird = Bird_new();
bird->Init(bird);
bird->Fly(bird);

调用继承的方法稍显麻烦:

((Animal*)bird)->Eat((Animal*)bird);

或者当EXTENDS语句在定义类时并不在第一句(下一篇将揭露各个宏的真实面目)时,最稳妥也显得繁琐一点的方式为:

SUPER_PTR(bird, Animal)->Eat(SUPER_PTR(bird, Animal));