搜索
bottom↓
回复: 35

[交流][微知识]模块的封装(三):无伤大雅的形式主义

  [复制链接]

出0入296汤圆

发表于 2014-5-28 10:42:39 | 显示全部楼层 |阅读模式
本帖最后由 Gorgon_Meducer 于 2017-5-31 00:10 编辑


[交流][微知识]模块的封装(三):无伤大雅的形式主义

    在前面的讨论中,我们介绍了如何在必要的情况下,利用结构体和联合体在C语言中引入应用所必须的一些面向对象的
特性,例如,封装继承、派生以及覆盖。从功能性上来说,前两章的内容就足够满足应用了;但从形式上来说,模块封装
(二)中提供的类的继承方式在使用的时候还有那么一点不爽,所以,作为弥补一小部分有代码洁癖人的心头缺憾,今天我
们就来讨论一种在不增加任何实质性系统开销的前提下实现对象成员访问的形式上的改进

特别提示:本帖仅用于娱乐,C++党请本着娱乐的心情吐槽和嘲讽,避免引起C党在代码效率上的反驳。形式主义
                就是形式主义,没有用,就是自己看着舒服


    首先我们来说说哪里不爽了,举个例子,我们定义了一个接口,用于封装一些针对存储器页(Page)的操作:

  1. //! \name memory page access interface
  2. //! @{
  3. DEF_INTERFACE(i_mem_page_t)

  4.     fsm_rt_t (*PageWrite)   (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  5.     fsm_rt_t (*PageRead)    (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  6.     fsm_rt_t (*PageErase)   (mem_t *ptMal, uint32_t wPageAddress);                  
  7.     fsm_rt_t (*Erase)       (mem_t *ptMal);

  8. END_DEF_INTERFACE(i_mem_page_t)
  9. //! @}
复制代码

这个接口本身没什么太特别的,就是定义了针对所有以Page的基本单位的存储器常见的四个操作:页写,页读,页擦除和
整片擦除。接口类型叫做i_mem_page_t,顾名思义,就是一个针对memory page的接口,详细内容不提也罢。接下来我
们又定义了另外一个接口i_mem_t,这个接口试试图为存储器抽象一个更通用的操作方式,比如Init, Open, Close之类的,
为了方便日后的统一操作,这里不妨也定义成接口i_mcb_t(这里mcb是memory control block的缩写),新的接口i_mem_t
就同时继承了i_mem_page_t和i_mcb_t,如下所示:


  1. //! \name memory control block
  2. //! @{
  3. DEF_INTERFACE(i_mcb_t)

  4.     fsm_rt_t        (*Init)         (mem_t *ptMal, void *ptCFG);               
  5.     fsm_rt_t        (*Finish)       (mem_t *ptMal);                             
  6.     mem_info_t      (*Info)         (mem_t *ptMal);                             
  7.     fsm_rt_t        (*Open)         (mem_t *ptMal);                             
  8.     fsm_rt_t        (*Close)        (mem_t *ptMal);                             
  9.     mem_status_t    (*GetStatus)    (mem_t *ptMal);

  10. END_DEF_INTERFACE(i_mcb_t)   
  11. //! @}

  12. //! \name Memory Abstraction Layers
  13. //! @{
  14. DEF_INTERFACE(i_mem_t)

  15.     i_mcb_t CONTRL;                     
  16.     i_mem_page_t PAGE;                  
  17.     ...
  18.         
  19. END_DEF_INTERFACE(i_mem_t)
  20. //! @}
复制代码

    好的,问题来了:对一小部分人来说,下面的代码是有点不可容忍的,套用一句话说就是“好焦虑”。好焦虑对吧?
不要怀疑,为了凸显这种莫名的焦虑,我特别用了红色。


  1. i_mem_t *ptMEM = xxxxxx;
  2. ...
  3. ptMEM->CONTRL.Open();    //! 打开存储器
复制代码




    这里所谓的多按一次“.”是针对那些有联想提示功能的IDE说的,因为通过上述方法,所谓的实现接口(Implement)实际
上是通过简单的添加接口的成员变量来实现的,在这种情况下访问继承接口的成员函数必须要先通过这个成员变量,比如例子
中的CONTRL。对某些人来说上面的例子代码应该支持这种形式才好:

  1. i_mem_t *ptMEM = xxxxxx;
  2. ...
  3. ptMEM->Open();    //!< 直接打开存储器
  4. ...
  5. i_mcb_t *ptMCB = &(ptMEM->CONTRL);    //!< CONTRL 仍然存在
复制代码

    简单来说就是既可以直接访问被继承的接口的成员,又可以保留对接口的直接引用。做到第一点并不难,只要通过下面的
代码就可以了:

  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)

  4.     fsm_rt_t        (*Init)         (mem_t *ptMal, void *ptCFG);               
  5.     fsm_rt_t        (*Finish)       (mem_t *ptMal);                             
  6.     mem_info_t      (*Info)         (mem_t *ptMal);                             
  7.     fsm_rt_t        (*Open)         (mem_t *ptMal);                             
  8.     fsm_rt_t        (*Close)        (mem_t *ptMal);                             
  9.     mem_status_t    (*GetStatus)    (mem_t *ptMal);

  10.     fsm_rt_t (*PageWrite)   (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  11.     fsm_rt_t (*PageRead)    (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  12.     fsm_rt_t (*PageErase)   (mem_t *ptMal, uint32_t wPageAddress);                  
  13.     fsm_rt_t (*Erase)       (mem_t *ptMal);

  14. END_DEF_INTERFACE(i_mem_t)
复制代码

看到这里,一群人就要“呵呵”了,我也“呵呵”,你会管这个叫做对接口i_mcb_t和i_mem_page_t的继承么?你骗谁呢!
    a . 如果修改了i_mcb_t或者i_mem_page_t的内容,我们还需要一起修改所有“所谓继承”了他们的接口,这哪里是
        面向接口开发?这简直就是面向麻烦啊,我都不会同意
    b. 有些应用就是纯粹的面向接口(虚函数表)的,因此必须要保留对原有接口的引用能力。因此CONTRL和PAGE是
        必须要保留的。

    要想同时保留名副其实的“继承”,又想有能力直接访问被继承接口的成员,就只有借助ANSI-C11标准引入的匿名结构
体了。上述例子的解决方案如下:

  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)

  4.     union {                                 
  5.         struct {                           
  6.             fsm_rt_t        (*Init)         (mem_t *ptMal, void *ptCFG);               
  7.             fsm_rt_t        (*Finish)       (mem_t *ptMal);                             
  8.             mem_info_t      (*Info)         (mem_t *ptMal);                             
  9.             fsm_rt_t        (*Open)         (mem_t *ptMal);                             
  10.             fsm_rt_t        (*Close)        (mem_t *ptMal);                             
  11.             mem_status_t    (*GetStatus)    (mem_t *ptMal);
  12.         };                                 
  13.         i_mcb_t CONTRL;                     
  14.     };                                      
  15.     union {                                 
  16.         struct {                           
  17.             fsm_rt_t (*PageWrite)   (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  18.             fsm_rt_t (*PageRead)    (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
  19.             fsm_rt_t (*PageErase)   (mem_t *ptMal, uint32_t wPageAddress);                  
  20.             fsm_rt_t (*Erase)       (mem_t *ptMal);
  21.         };                                 
  22.         i_mem_page_t PAGE;                  
  23.     };
  24.    
  25. END_DEF_INTERFACE(i_mem_t)
  26. //! @}
复制代码


     其实匿名一直是很强大的——比如匿名结构体啊,匿名联合体啊,比如,其实上面的方式可以写成如下的形式(感谢15楼剧透):

  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)

  4.     union {                                 
  5.         i_mcb_t;                    
  6.         i_mcb_t CONTRL;                     
  7.     };                                      
  8.     union {                                 
  9.         i_mem_page_t;                                 
  10.         i_mem_page_t PAGE;                  
  11.     };
  12.    
  13. END_DEF_INTERFACE(i_mem_t)
  14. //! @}
复制代码

如果你不想保留对原有接口的引用,你甚至可以这么写:

  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)
  4.                           
  5.         i_mcb_t;                              
  6.         i_mem_page_t;                                 
  7.    
  8. END_DEF_INTERFACE(i_mem_t)
  9. //! @}
复制代码

肿么样,强大的cry了吧。接下来,让我们把形式主义进行到底,首先来定义一个宏:

  1. //! \brief macro for inheritance

  2. #define INHERIT_EX(__TYPE, __NAME)  \
  3.             union {                 \
  4.                 __TYPE  __NAME;     \
  5.                 __TYPE;             \
  6.             };

  7. /*! \note When deriving a new class from a base class, you should use INHERIT
  8. *        other than IMPLEMENT, although they looks the same now.
  9. */
  10. #define INHERIT(__TYPE)             INHERIT_EX(__TYPE, base__##__TYPE)

  11. /*! \note You can only use IMPLEMENT when defining INTERFACE. For Implement
  12. *        interface when defining CLASS, you should use DEF_CLASS_IMPLEMENT
  13. *        instead.
  14. */
  15. #define IMPLEMENT(__INTERFACE)      INHERIT_EX(__INTERFACE, base__##__INTERFACE)

  16. /*! \note if you have used INHERIT or IMPLEMENT to define a CLASS / INTERFACE,
  17.           you can use OBJ_CONVERT_AS to extract the reference to the inherited
  18.           object.
  19.   \*/
  20. #define OBJ_CONVERT_AS(__OBJ, __INTERFACE)  (__OBJ.base__##__INTERFACE)
复制代码

然后自然就得到以下的代码:

  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)  INHERIT(i_mcb_t)  INHERIT(i_mem_page_t)
  4.     ...
  5.     ...    //! 继承后增加的成员
  6.     ...
  7. END_DEF_INTERFACE(i_mem_t)
  8. //! @}
复制代码


    对于i_mem_t的对象,如果我们想访问其继承的接口,可以通过下面的代码来实现:

  1.     i_mem_t *ptMEM = .....;
  2.     i_mcb_t *ptMCB = &OBJ_CONVERT_AS((*ptMEM), i_mcb_t);
  3.     ptMCB->Init(...)
  4.     ...
复制代码

或者

  1.     i_mem_t *ptMEM = .....;
  2.     i_mcb_t *ptMCB = REF_OBJ_AS((*ptMEM), i_mcb_t);
  3.     ptMCB->Init(...)
  4.     ...
复制代码

    当然,普通情况下,我们只要直接用ptMEM访问Init()方法就好了。上面的方法的方法是为面向接口开发所余留的能力。

    最后我们来谈一个C语言对“匿名”的支持问题。因为这是一个比较新的特性,很多C编译器并不默认支持。别的环境我们
就不管了,从事嵌入式开发,常见的几个C编译环境比如IAR,ARM_MDK,GCC等等都是支持的,但需要编译开关打开。
粘贴下面的代码到你的系统级头文件中,就可以让你的匿名代码在以上几个环境下都获得支持:

  1. /* -------------------  Start of section using anonymous unions  ------------------ */
  2. #if defined(__CC_ARM)
  3.   //#pragma push
  4.   #pragma anon_unions
  5. #elif defined(__ICCARM__)
  6.   #pragma language=extended
  7. #elif defined(__GNUC__)
  8.   /* anonymous unions are enabled by default */
  9. #elif defined(__TMS470__)
  10. /* anonymous unions are enabled by default */
  11. #elif defined(__TASKING__)
  12.   #pragma warning 586
  13. #else
  14.   #warning Not supported compiler type
  15. #endif
复制代码


全文完

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入296汤圆

 楼主| 发表于 2014-5-28 10:43:07 | 显示全部楼层
TO BE CONTINUME

出0入58汤圆

发表于 2014-5-28 10:48:43 | 显示全部楼层
前排支持。

出0入0汤圆

发表于 2014-5-28 11:02:14 来自手机 | 显示全部楼层
前排搬凳子听课~

出0入0汤圆

发表于 2014-5-28 11:13:09 | 显示全部楼层
占座听讲



出0入0汤圆

发表于 2014-5-28 11:38:26 | 显示全部楼层
MARK                     

出0入0汤圆

发表于 2014-5-28 11:57:54 | 显示全部楼层
刚开始看uml+oopc,我猜您是想说向上转型的事,接口把内部函数和可供外部调用的函数分开来,但是例子里面的写法显得好粗暴,对外就直接是两个不同的结构体
是不是呢?

出0入0汤圆

发表于 2014-5-28 11:57:56 | 显示全部楼层
看直播

出0入296汤圆

 楼主| 发表于 2014-5-28 12:08:31 | 显示全部楼层
dongfo 发表于 2014-5-28 11:57
刚开始看uml+oopc,我猜您是想说向上转型的事,接口把内部函数和可供外部调用的函数分开来,但是例子里面的 ...

我要讨论的不是这个,把public的成员函数和private的成员函数分开始通过掩码结构体实现的,模块封装(二)里面有提到的。
你看下面的代码就知道了:

  1. #define EXTERN_CLASS_IMPLEMENT(__NAME,__INTERFACE,...) \
  2.     typedef union __NAME __NAME;\
  3.     __VA_ARGS__\
  4.     union __NAME {\
  5.         const __INTERFACE method;\
  6.         uint_fast8_t chMask[(sizeof(struct {\
  7.             const __INTERFACE method;

  8. #define END_EXTERN_CLASS_IMPLEMENT(__NAME, __INTERFACE) \
  9.         }) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)];\
  10.     };
复制代码

公开的成员函数是通过接口形式来定义的,是可以利用xxxx.method.xxxxx来访问的。而私有的部分则隐藏在
xxxx.chMask里面。

出0入93汤圆

发表于 2014-5-28 12:18:57 | 显示全部楼层
C++党表示震惊!

ptMEM->CONTRL.Open();    //! 打开存储器
我很好奇,你到底是如何将this指针传递过去的,Open方法的参数都变没了,我觉得很强悍。
为了懒得多写一个点,那就不如将CONTROL匿名得了,连CONTROL.都可以不用写了。

出0入296汤圆

 楼主| 发表于 2014-5-28 12:35:33 | 显示全部楼层
takashiki 发表于 2014-5-28 12:18
C++党表示震惊!

我很好奇,你到底是如何将this指针传递过去的,Open方法的参数都变没了,我觉得很强悍。

哈哈,你已经抓到要点了,这个帖子其实很无聊……嘿嘿~就是娱乐下

出0入0汤圆

发表于 2014-5-28 13:05:01 | 显示全部楼层
回家再看看,哈哈哈

出0入0汤圆

发表于 2014-5-28 14:52:13 | 显示全部楼层
强势插入。

出0入0汤圆

发表于 2014-5-28 14:54:08 | 显示全部楼层
好吧,补C语言去,没用过匿名结构体和共用体啊

出0入93汤圆

发表于 2014-5-28 15:02:47 | 显示全部楼层
匿名结构/联合是C11标准,不是C99的,虽然很多C89的也支持。
匿名的你这么写不就不需要把接口全部抄一遍了么?
  1. //! \name Memory Abstraction Layers
  2. //! @{
  3. DEF_INTERFACE(i_mem_t)

  4.     union {                                 
  5.         i_mcb_t;                                   //这里匿名
  6.         i_mcb_t CONTRL;                     
  7.     };                                      
  8.     union {                                 
  9.         i_mem_page_t;                             //这里匿名
  10.         i_mem_page_t PAGE;                  
  11.     };
  12.    
  13. END_DEF_INTERFACE(i_mem_t)
  14. //! @}
复制代码

出0入296汤圆

 楼主| 发表于 2014-5-28 15:22:19 | 显示全部楼层
takashiki 发表于 2014-5-28 15:02
匿名结构/联合是C11标准,不是C99的,虽然很多C89的也支持。
匿名的你这么写不就不需要把接口全部抄一遍了 ...

感谢你的指正,我再去查查,应该是我弄错了。另外,你说的方法剧透了……剧透了……透了……了……

出0入0汤圆

发表于 2014-5-29 12:59:10 | 显示全部楼层
本帖最后由 mikal 于 2014-5-29 13:23 编辑

看了lz的 一 和 三,让我感觉,仅仅是编程技巧,但华而不实 (当然,这样的编程技巧我以前也不会,所以我也是崇拜你的)。

按照一般 三层架构系统分析,我看到的都是耦合!

有个疑问,lz是主攻c++还是 C ?

出0入296汤圆

 楼主| 发表于 2014-5-29 13:58:40 | 显示全部楼层
mikal 发表于 2014-5-29 12:59
看了lz的 一 和 三,让我感觉,仅仅是编程技巧,但华而不实 (当然,这样的编程技巧我以前也不会,所以我也 ...

C,说说你看到的耦合,我很感兴趣。

出0入0汤圆

发表于 2014-5-29 16:49:04 | 显示全部楼层
本帖最后由 mikal 于 2014-5-29 17:56 编辑
Gorgon_Meducer 发表于 2014-5-29 13:58
C,说说你看到的耦合,我很感兴趣。





    fsm_rt_t        (*Init)         (mem_t *ptMal, void *ptCFG);               
    fsm_rt_t        (*Finish)       (mem_t *ptMal);                             
    mem_info_t      (*Info)         (mem_t *ptMal);                             
    fsm_rt_t        (*Open)         (mem_t *ptMal);                             
    fsm_rt_t        (*Close)        (mem_t *ptMal);                             
    mem_status_t    (*GetStatus)    (mem_t *ptMal);

   以上实现的需要调用如下的实现,

   fsm_rt_t (*PageWrite)   (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
    fsm_rt_t (*PageRead)    (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer);  
    fsm_rt_t (*PageErase)   (mem_t *ptMal, uint32_t wPageAddress);                  
    fsm_rt_t (*Erase)       (mem_t *ptMal);

   那么这样非同一级别的操作放到一个结构体中来描述,其本身就是耦合!

   按照我们以往xx公司的规范,这是layer耦合,因为这种情况下,我们需要应用组和驱动同时来维护这个结构体!

出0入0汤圆

发表于 2014-5-29 19:13:58 | 显示全部楼层
听课来了,比较新鲜

出0入296汤圆

 楼主| 发表于 2014-5-29 23:24:26 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2014-5-29 23:31 编辑
mikal 发表于 2014-5-29 16:49
fsm_rt_t        (*Init)         (mem_t *ptMal, void *ptCFG);               
    fsm_rt_t   ...


i_mcb_t并不需要调用i_mem_page_t
他们在层次上是并列关系,他们之间没有任何调用关系。因此不存在layer级别的耦合。你误会这两个接口应该是
我没有解释清楚,这里的open并不是file的open,而仅仅是open底层驱动,使得后续针对page的操作是有效的。
举个简单的例子,比如操作一个SPI Flash,常见的步骤如下:

Init(xxx);      //!< 初始化SPI
Open(xxx);   //!< 打开SPI

Info();         //!< 获取SPI Flash的信息,比如page大小,page数量等等
Page Read / Write

Close();       //!< 关闭SPI
Finish();       //!< 释放SPI

可见,这里的i_mcb_t并不是类似Linux“设备文件”的操作,他们并不会涉及到具体的memory读写操作,与i_mem_page_t
不存在调用关系。


另外,i_mem_t 是继承了i_mcb_t和i_mem_page_t,i_mcb_t和i_mem_page_t可以由不同的团队单独维护,
当他们之中任何一个接口发生了变化,i_mem_t是不需要变更的。并不存在你说的layer耦合。

我想我要解释下i_mcb_t和i_mem_page_t的分工,这个分工就类似wifi模块,ap接口和socket接口的分工。
具体针对memory操作的服务只需要依赖i_mem_page_t,而无需关心i_mcb_t。同样,关心i_mcb_t的服务
并不关心别人怎么使用这个memory。所以,从团队职能划分上,处理page相关的读写服务的,维护i_mem_page_t;
处理存储器抽象管理(挂载)等等的只负责基于维护基于i_mcb_t之上的服务。而驱动编写者则根据约定针对
具体硬件实现i_mcb_t和i_mem_page_t。

总结来说,这里牵涉到三波团队:
负责上层memory读写服务的团队(依赖和维护i_mem_page_t);
负责上层存储器管理的团队(依赖和维护i_mcb_t);
为具体器件编写具体驱动的团队(实现i_mcb_t和i_mem_page_t)

经过我这样的解释,你觉得这里存在什么耦合么?

出0入0汤圆

发表于 2014-5-29 23:39:35 | 显示全部楼层
跟踪收藏

出0入0汤圆

发表于 2014-5-30 10:08:04 | 显示全部楼层
本帖最后由 mikal 于 2014-5-30 10:11 编辑
Gorgon_Meducer 发表于 2014-5-29 23:24
i_mcb_t并不需要调用i_mem_page_t
他们在层次上是并列关系,他们之间没有任何调用关系。因此不存在layer ...


其实我看很多问题,首先看第一感觉,因为我的第一感觉还可以,如果觉得总是让我觉得哪里有些不舒服,
那么我就认为,这个东西和我想的,我的认知范畴有些不一样!

之所以 针对你写的 一 和 三 ,我看了后,有2种感觉,第一种感觉,很多编程技巧真的很好,值得我去学习,也许
我以前知道一些,但是肯定比不上你的科班。但第二种感觉,就是这样的技巧使用好像就是有点冗余。当然这仅仅
是站在我自身认知的角度看问题,也许对,也许错,也许都不错。但不管怎么样,首先不能打消我对你的敬佩,但
同时也抹杀不掉,我对你某些方面的不认同。

其实我对i_mcb_t的理解 确实如你说的类似linux的处理,因为如果模块化,抽象化的目的就是易移植,直观化,可靠化!
但在你举了spi的memory的例子后,更加让我觉得这个设计的耦合了。我们从高一层的app来看问题,当我要对memory
操作的时候,还需要对i_mcb_t和i_mem_page_t处理,也就是说,上层app需要写memory,需要你说的,

Init(xxx);      //!< 初始化SPI
Open(xxx);   //!< 打开SPI

Info();         //!< 获取SPI Flash的信息,比如page大小,page数量等等
Page Read / Write

Close();       //!< 关闭SPI
Finish();       //!< 释放SPI

那这样很痛苦了,当然,你可能会继续举例子,我上面还有fileopen filewrite 之类的更高抽象层,但是依然不能解释,2类驱动
的抽象层合并一个结构体来处理的耦合。当然,不是说耦合不好,很多时候,耦合是必须的,因为耦合和非耦合其实也是一个
平衡的结果。

当然,整理来说,你的观点还是很优秀的,起码值得我从战术角度去思考一些系统架构!因为以后或许在小型架构系统中,我会
采用你这样的耦合来达到模块化精简设计!因为我在设计过程中,遇到一些并行驱动的问题,在小型系统中,一直没有找到一个
让我满意的方案。这次,让我看到了一些思路。所以,还是要谢谢你的。

在我的设计之路,我从来不用“大而全”的方法做设计,而是因地制宜,哪种合适就用哪种。对我来说,一个产品设计的原则是,在稳定的
前提下,提高效率和节能减排!

希望以后能看到更加优秀的思想!


出0入296汤圆

 楼主| 发表于 2014-5-30 10:35:20 | 显示全部楼层
mikal 发表于 2014-5-30 10:08
其实我看很多问题,首先看第一感觉,因为我的第一感觉还可以,如果觉得总是让我觉得哪里有些不舒服,
那 ...

非常感谢你的回复。我觉得我们的想法其实是一致的,我从来不认为所有人的想法都要一致,认同就必须全盘认同之类
我觉得之所以交流有意义就不是谁要说服谁,而是因为大家都有自己各自不同的想法,所以交流才有意义,否则就变成
一种强制的思维灌输和暴力。另外,你所说的应地制宜,具体问题具体对待,我是从另外一个角度阐释的:嵌入式系统
是专用计算机系统,因此不应该追求通用(就是你说大而全),应该强调专用性,也就是具体问题具体对待。嵌入式系
统的专用特性就要求,不能有泛泛的通用,而只能在严格界定的范围内,在不增加(或在成本允许的范围内)做到最大
限度的通用。

这个例子,我只是想介绍匿名结构体在“形式主义”上的用途,并不是讨论存储器抽象层的。发展成针对存储器的讨论,
实在是出乎我意料,我这个例子没有好好准备,也没有认真思考。但是你的讨论让我开始想,有必要针对这个问题歪下
楼,做更多的讨论。其实我觉得针对小系统,目前的MAL已经足够了,但我其实还是非常好奇,如果是一种“不耦合”的
接口形式,针对同样的例子,应该是什么样的呢?能不能举出一个例子让我理解到问题的根本呢?

我的观点是,i_mcb_t 和i_mem_page_t 是两个并行的接口,在小系统下,因为精简的需要,上层应用需要通过一个
统一的接口来首先操作i_mcb_t完成对存储器的初始化等操作,再通过i_mem_page_t完成具体的存储器操作。小系统
需要进退皆可的策略,当资源吃紧,或者应用本来就很小的情况下,直接操作i_mcb_t和i_mem_page_t是很经济的做
法,既能复用那些基于这些接口的服务函数,又不用引入更高层的抽象服务。当系统较大,资源较为丰富时,则可引入
更高层的抽象结构来替用户应用接管i_mcb_t和i_mem_page_t的操作,从而给用户提供更为简化的操作接口,其实你
如果注意一下会发现所有的接口都是针对mem_t这个类型的。而这就是最终用户需要使用的,用户最终不会接触到
i_mem_t,他们使用的是mem_t,高层会提供对mem_t的访问,从而屏蔽反锁的针对i_mcb_t和i_mem_page_t的操作。

希望听听你的看法,以及非“耦合”接口设计的想法。耦合其实不是一个非0即1的词,耦合是一个“度”,耦合度高对模块
化是有害的,但是在一定范围内对适当的设计是存在效率上的优势的,所以,正如你所说的,并非去去掉耦合,而是应
该更具应用环境以及未来的扩展性需求来“降低耦合度”。针对这个设定,希望我们以后的讨论能先说明应用环境,避免
拿MCU和Linux进行完全不是一个量级的比较。

出0入0汤圆

发表于 2014-5-30 13:19:59 | 显示全部楼层
Gorgon_Meducer 发表于 2014-5-30 10:35
非常感谢你的回复。我觉得我们的想法其实是一致的,我从来不认为所有人的想法都要一致,认同就必须全盘认 ...

1、首先向你表示歉意,把你的题引偏了,其实,从切入主题来讲,你观点,描述的都合情合理,没有差错。
     只是,我这个人看问题角度老是从第一感觉出发,总想从别人的思想中看到一些背后的东西,性格使然,改不了。

2、直接切入已经歪了的正题:),讲讲我对耦合的看法。诚如你所说,耦合是一个度,我非常赞同,另外layer耦合,
     从你的解释来看目前也不存在。那么后面我还觉得耦合,是从memory的种类来分析的。我们都知道如今memory
     的接口很多,IIC,SPI,并口,内部等等,那么既然在做memory抽象出的时候,我们不可能仅仅只考虑spi吧,
     但是如果把所有接口都考虑,岂非有大而全了?基于这样的思路,我把这类耦合称为可扩展性耦合,因为我们做
     抽象层就是为了稳定可靠,可移植,可扩展而服务的。当然,如果你后面举例子不是直接体现spi,而是直接体现
     memory,那么这个耦合没有了。比如open其实就是open memory,而不局限是spi,那么在memory层的驱动,
    根据open memory的类型来确定选择什么样的接口。另外一个耦合,就是参与度耦合,对于一个模块,参与人之间
    多方互动越少越好,那么假如人手足够,或者要赶进度,那么针对本楼的结构体,我们需要3人参与,spi驱动,memory
    驱动,中间层,在这三人中,中间层必须同时和spi驱动,memory驱动2人沟通,同时spi驱动和memory也需要沟通,
   那这样的参与度是3次,但如果我们把i_mcb_t移到memory驱动层,我们会发现,参与度是2了。当然这个参与度耦合
  是我从开发整体规划角度看的,可能很多人未必赞同,但我还是喜欢这么去评价!也喜欢这样做设计。

3、关于理论的诉求,其实嵌入式行业做了10多年,一直走的是实践,然对理论懂的很少很少,不是摒弃理论,然仅仅是
   觉得没有太多的时间去分析,去看看,再剥离出自己有用的东西,我更喜欢在网络上直接找一些我想用的东西,在简化或者
   修改成我的东西,或者与别人的沟通中,提升自己的认知。几年前,我可能还处在斤斤计较某个模块size,time,稳定可靠,
   可移植,可扩展。如今我思考的是平台的延续性,参与度的分析,联合开发的分解!同时以逆向工程角度去分析正向开发。
   再说白了,就是人力成本,我们要降低人力成本,所以对非耦合的设计思路,我本着不是没有,不是不高的原则,而是从
   逆向工程和平台延续性,参与度去分析。逆向,就是希望提高耦合度,增加破解难度,平台延续性,是从稳定,效率角度,
   参与度就是降低人力成。

   好久没有再做过整体设计了,现在再来讨论,感觉有些吃力!

出0入296汤圆

 楼主| 发表于 2014-5-30 15:59:20 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2014-5-30 16:05 编辑
mikal 发表于 2014-5-30 13:19
1、首先向你表示歉意,把你的题引偏了,其实,从切入主题来讲,你观点,描述的都合情合理,没有差错。
   ...


没啥歉意的,抛砖引玉是我说滥了的词。有讨论就有收获。
memory种类很多,我这里都有统一的处理,上述i_mcb_t其实把你说的都囊括了,只是顺手举了SPI的
例子,不必太纠结。i_mcb_t就是一个抽象的接口,具体实现要根据具体硬件来处理。有句话怎么说的
来着,面向接口开发的精髓就是“用扩展替代修改”。

沟通的问题是我一直以来处理模块化的核心出发点。我曾经在内部培训的时候给团队成员说过,团队核心
的问题不是技术问题,而是沟通问题,这也是模块化的出发点。其实沟通不光发生在成员之间,就是一个
人开发,这个人在不同时间点上也算是不同的个体——为什么这么说呢,举例来说一个月前你写了一个模块,
后来你学了一些新的技术,一个月后你用一个月前写的代码,你就产生沟通的问题了——因为忘记了,你需
去读代码了,甚至在缺乏文档地情况下,完全读不懂。这种问题其实很普遍。模块化的目的就是为了建立
黑盒子,建立黑盒子的目标就是减少阅读代码带来的沟通开销,只依赖接口,直接使用服务,就可以加快
开发速度。我们应该说都已经从人力的角度在考虑问题了。

关于参与度,我对团队的要求是多角色,就是做什么模块开发从什么角度考虑,避免代入别的角色。这需要
一套规范和长期的培训和训练,具体就不展开了,总之从效果上看值得一做。总体来说,我是向着黑盒子的
方向发展的,怎样有利于黑盒子的构建我就怎么做,至于耦合度只是衡量标准之一。我从来不相信有一开始
就构建的很完美的架构,我都是和团队说,先把原型做出来,然后根据后续的需要,该合并的合并,该抽象
的抽象,时间长了,就有了一个好的架构的积累,你经验的多寡并不能改变这个,只能说经验丰富的人可以
加快这个进程。我有一个观点就是:如果你不能把一个新事物先做复杂了,那么你就肯定没法把他做到最简
单最优化。

降低人力,意味着增加耦合,同时意味着你在较少的人身上追加了太多的价值,这无形中增加了团队因为
成员离职带来的技术漂变的风险。所以,耦合还是尽可能低的好。急的项目可以按照紧急的方式去处理,但
事后有时间了,一定要把省掉的工作补上,降低耦合度,降低风险,为下一次复用模块做好准备。

出425入0汤圆

发表于 2014-11-17 11:46:53 | 显示全部楼层
这些讨论都是高深的。我当菜鸟10年,现在还是菜鸟。

出0入0汤圆

发表于 2014-11-17 12:39:17 | 显示全部楼层
这一个系列很好,是C++,一般单片机得先设置成C++的编译吧!

出0入296汤圆

 楼主| 发表于 2014-11-17 15:22:06 | 显示全部楼层
tuohang2013 发表于 2014-11-17 12:39
这一个系列很好,是C++,一般单片机得先设置成C++的编译吧!

都是C,不是C++

出0入0汤圆

发表于 2014-11-17 17:59:15 | 显示全部楼层
我非常支持,绝对的支持!

出0入0汤圆

发表于 2014-11-17 18:14:49 | 显示全部楼层
听课咯~~~~~~~~~~~~~~~~~~

出0入296汤圆

 楼主| 发表于 2015-3-31 14:16:16 | 显示全部楼层
更新INHERIT和IMPLEMENT的定义,增加OBJ_CONVERT_AS,这个宏的灵感来自于C#

出0入0汤圆

发表于 2016-3-9 15:11:19 | 显示全部楼层
看了这么多的评论,表示我需要大量的学习和经验。

出0入0汤圆

发表于 2017-6-1 10:37:06 | 显示全部楼层
路过挖一下。。。。。

出140入115汤圆

发表于 2018-9-1 11:10:39 | 显示全部楼层
路过我也挖一下。。。。。

出0入0汤圆

发表于 2018-9-7 13:17:17 | 显示全部楼层
期待精彩de后续
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-2 05:35

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表