解耦设计

一般设计设计现在,构架设计设计未来。设计未来一个很重要的考量要素就是“解耦”设计。


直观理解“解耦”,就是我可以替换某个模块,对原来系统的功能不造成影响。比如Windows 95, 98, 2000, 7, 8, 10都可以直接运行在兼容PC上。反过来,奔腾,奔腾II,Core做出来的不同硬件,都可以跑Windows。


在构架设计中,解耦是一种成本很高的设计,因为比如说你本来点亮一个LED灯,是可以这样写的:


*(volatile u32 *)(0x1234567) = 32;


但在为了解耦,你需要这样写:

#ifdef PLATFORM1
*(volatile u32 *)(0x1234560) = 32;
#elif defined(PLATFORM2)
*(volatile u32 *)(0x89abcde0) = 128;
#elif defined(PLATFORM3)
*(volatile u32 *)(0x89abcde0) = 256;
mb()
*(volatile u32 *)(0x1234560) = 32;
usleep(10);
*(volatile u32 *)(0x1234560) = 64;
#endif


如果我们不约束接口,解耦设计就会变成一个僵梦。我都不说增加的代码量了,就这种一堆#ifdef,就能让你看得头都大了(qemu的代码就是其中一个坏例子,和Linux Kernel的代码对比一下就能比出来,但因为qemu的复杂度比Linux Kernel还是小多了,维护者们都不在乎而已)


很多工程师解决这个问题的方法是设计“灵活接口”,这种灵活接口包括(而不限于):

i. 使用“消息接口”取代函数或者寄存器接口

ii. 使用“命令”取代寄存器接口

iii. 使用HAL层“封装”差异

iv. 用配置文件改变模块的不同行为 


我这个文档要表达的观点是,就用这种方法实现“解耦”,基本上是缘木求鱼,水中捞月。


我们前面已经说过了,“解耦”的目的是可以替换接口上的一个模块。那么接口通讯变成消息了,改变了两个模块之间的依赖关系吗?显然没有——那有什么用?你把消息通讯上的一个模块换了个版本,或者把配置文件调整了一下,编译器确实没有报错了,但模块的行为如果发生了改变,这个程序还能正常运行吗?(如果能正常运行,你用函数接口又有何妨?)


这个理由对于硬件的寄存器定义是一样的,用“命令队列”取代原来直接的寄存器访问,看起来写寄存器是不会报错了,但命令字进入硬件,你还能正确处理吗?如果你能正确处理,是命令队列还是寄存器,又有什么所谓呢?


所以,想简单引入一个这样的方法来说明自己进行了“解耦”设计是很不靠谱的。


HAL也是,比如前面那个点灯的操作,你抽象这样一个接口:


void light_switch(bool on_or_off);


看起来你抽象了差异。但假如你后面选择的灯是有多级亮度的呢?你的亮度从什么地方传递过去呢?

很多人都看不懂Android的HAL设计,Android的HAL称为“硬件抽象层”,但它的设计不是来自对硬件的抽象的,它是来自对用户的需求分析的。我在有短信的时候要点亮呼吸灯,我抽象这样一个接口:


void light_on_message();


我不管你选了什么灯,也不管你是几级亮度,我只要你能说明现在来消息了,你别找我要亮度这个数据,我不关心。这样这个接口就比较有保证了,这才是HAL的本意。


所以,HAL的设计基础是需求分析,不是硬件设计抽象,直接进行这种抽象而跳过市场预判和需求分析,是一种鸵鸟政策,不肯面对自己要面对的最难的问题。


除此以外,在很多时候,仅仅考虑数据传递的充要性是不够的,我们还要面对性能引起的细节问题。比如我做一个加速器引擎。用户态给定一组数据,按某种格式A排列,用系统调用进入内核,驱动的格式是B,这样就需要做一个转换,然后写入硬件,结果硬件要求的格式是C,这又发生一次转换。这个过程中,接口倒真的不需要修改。但这样一来,还加个鬼速啊。


所以,用户程序,驱动程序,加速器硬件的设计者,是没有任何机会可以各自为政的,你们必须坐下来,先完成你们对这个特性的需求分析,考虑所有未来的变数,然后定义一个可以从上到下贯通的接口,才能独立聚焦各自的模块做模块设计。


这说起来是个简单的道理,但一旦交付压力上来,设计师们就开始装傻了。但我告诉你,项目出问题的时候,倒霉的肯定少不了你,别以为你那些小聪明能帮助得了你什么。



1.从汇编、C语言为起点,十年FPGA开发设计经验总结

2.成为一个软硬件通吃的技术大牛,有一点很重要!

3.如何设计嵌入式系统?带你理解一个小型嵌入式操作系统的精髓

4.C语言访问MCU寄存器,有两种方式可以采用!

5.2018年第3期《单片机与嵌入式系统应用》电子刊新鲜出炉!

6.了解51单片机执行指令的过程,你会理解更深刻!

免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。

C++中常用的解耦设计模式包括工厂模式、抽象工厂模式、适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式、观察者模式和命令模式等。这些设计模式的共同点是将程序的业务逻辑与实现细节分离,从而提高代码的可读性和可维护性,同时也方便了代码的重用和扩展。 其中,工厂模式和抽象工厂模式可以将对象的创建和使用分离,使得程序更加灵活;适配器模式可以将不兼容的接口转换为兼容的接口,使得不同的类可以协同工作;装饰者模式可以动态地为对象添加新的功能,而不需要修改原有的代码;代理模式可以为其他对象提供一个代理,从而控制对该对象的访问;外观模式可以为复杂的子系统提供一个简单的接口,使得客户端可以更加方便地使用该子系统;桥接模式可以将抽象部分和实现部分分离,从而使得它们可以独立地变化;组合模式可以将对象组合成树形结构,从而使得客户端可以像处理单个对象一样处理整个树形结构;享元模式可以共享对象,从而减少内存的使用;观察者模式可以将对象的状态和行为分离,从而使得对象可以更加灵活地响应变化;命令模式可以将请求封装成对象,从而使得请求可以被记录、撤销和重做。 下面是一个使用工厂模式的例子: ```c++ #include <iostream> using namespace std; // 抽象产品类 class Product { public: virtual void use() = 0; }; // 具体产品类A class ProductA : public Product { public: void use() { cout << "ProductA" << endl; } }; // 具体产品类B class ProductB : public Product { public: void use() { cout << "ProductB" << endl; } }; // 抽象工厂类 class Factory { public: virtual Product* createProduct() = 0; }; // 具体工厂类A class FactoryA : public Factory { public: Product* createProduct() { return new ProductA(); } }; // 具体工厂类B class FactoryB : public Factory { public: Product* createProduct() { return new ProductB(); } }; int main() { Factory* factory = new FactoryA(); Product* product = factory->createProduct(); product->use(); delete product; delete factory; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值