嵌入式工程师超长经验分享:从单片机coder到嵌入式programer的简单历程
扫描二维码
随时随地手机看文章
其实UML就是一个工具,提供了用例图、顺序图、活动图、类图、状态机图、部署图、包图等工具,辅助工程师完成:分析->设计->实施->测试的整个过程。每个过程都有细分,例如分析阶段:首先是需求分析,然后有系统分析,再次有对象结构分析等。需求分析阶段会使用到用例图、顺序图、状态机图、顺序图等,需求分析阶段的最重要的要素是软件的外部功能视图和使用场景等。其中前者使用用例图表述,它也提供了沟通用户和开发者的桥梁;后者用顺序图或状态机图等表述,提供了系统每个功能实现的可能路径。其他过程和需求分析阶段类似,这里篇幅所限就不再一一提及。UML就是这样同我们的设计过程关联起来的。
将面向对象的方法用于MCU是有必要的,也是可能的,当然也是很有效的。这样的努力最起码可以拉近mcu开发者同其他领域的C开发者之间的距离,弥补那道似乎难以逾越的鸿沟,比如看到linux内核代码时,你会发现原来如此亲切。当然,随着对面向对象方法的深入理解,你会发现C++也不再那么让你不知道如何使用,或者把C++用得像面向过程的语言一样。当然本人C++菜鸟,还望高手指教。
然而面向对象的方法也非一蹴而就,一朝搞定,它是一个循序渐进的过程,特别是应用与mcu这样的平台,好多东西是靠摸索出来的。如何开始,先从何处下手是个问题。
21ic同仁liufb提议:“正如《重构与模式》所说:如果想成为一名更优秀的软件设计师,了解优秀软件设计的演变过程比学习优秀设计本身更有价值,因为设计的演变过程中蕴藏着大智慧。”
我决定发掘一下我近十年以来的阶段性C代码,试图去发现一点什么,这个我之前还从未尝试过,能找到的一起的代码也寥寥无几。不过我觉得值得一试,那就从此开始吧。
bit FUN,Shift_on,Add_on,Sub_on,fun_flag;
bit dspflagz1,dspflagz2,dspflagz3;
unsigned char z1,z2,td3,working,DSP_m,DSP_n;
unsigned char l1,l2,r,m;
bitflagv,flagy,flags,flag0,flagx,beepflag1,beepflag2,flagt,flagw;
bit working_on,function_on, AINTSYR_on,AINTSYW_on,BINTSYR_on,BINTSYW_on ;
bitprogram_on,program_woking,up_flag,down_flag,up_on,down_on;
unsigned char AINTSY_state, BINTSY_state, function_state;
unsigned char tx1,tx2,tx3,tw,TX,t;
unsigned char display_state ,x1,x2,w1,w2;
unsigned char program_state,program_working;
unsigned char clk_number;
unsigned char code DS[]={0,33,63,86,100,86,63,33};
unsigned chards_curtime[6]={0x05,0x03,0x02,0x01,0x07,0x06};
unsigned char clk_data[6]={06,1,1,0,0,1};
unsigned char set_time[6];
sbit switch_work= 0xB0;
sbit switch_function=0xB1;
sbit switch_program=0xB2;
sbit switch_up=0x90;
sbit switch_down=0x91;
sbit switch_AINTSYR=0x92;
sbit switch_AINTSYW=0x93;
sbit switch_BINTSYR=0x94;
sbit switch_BINTSYW=0x95;
sbit RS=0xA2;
sbit RW=0xA1;
sbit E=0xA0;
sbit CS2=0xA3;
sbit CS1=0xA4;
sbit DACS1=0xA7;
sbit DACS2=0xA6;
sbit DACS3=0xA5;
sbit ds_sclk=0xB3 ; /*初始化变量*/
sbit ds_io=0xB4;
sbit ds_rst=0xB5;
//////////////////////////////////////////////////////////////////////////////////////////////////////
void text(void)
{
bit Flag_add; /*加一标志*/
bit Flag_sub; /*减一标志*/
unsigned char max_value; /*各时间单位的最大值*/
unsigned char min_value; /*各时间单位的最小值*/
/*if(FUN==1)
{ */ /*定义标志位*/
if(switch_work==0) /*移位键*/
{
if(Shift_on==0)
{
Shift_on=1;
buzzer();
clk_number++;
if(clk_number>6)clk_number=1;
}
}
else
Shift_on=0;
if(switch_up==0) /*加一键*/
{
if(Add_on==0)
{
Add_on=1;
buzzer();
Flag_add=1;
}
}
else
Add_on=0;
if(switch_down==0) /*减一键*/
{
if(Sub_on==0)
{
Sub_on=1;
buzzer();
Flag_sub=1;
}
}
else
Sub_on=0;
switch(clk_number)
{
case 1: max_value=99;min_value=0;break;
case 2: max_value=12;min_value=1;break;
case 3: if(clk_data[1]==1||
clk_data[1]==3||
clk_data[1]==5||
clk_data[1]==7||
clk_data[1]==8||
clk_data[1]==10||
clk_data[1]==12)
max_value=31; /*1,3,5,7,8,10,12*/
else if(
clk_data[1]==4||
clk_data[1]==6||
clk_data[1]==9||
clk_data[1]==11)
max_value=30; /*4,6,9,11*/
else if((clk_data[0]%4==0)||clk_data[0]==0)
max_value=29; /*闰年*/
else
max_value=28;
min_value=1;
break;
case 4: max_value=23;min_value=0;break;
case 5: max_value=59;min_value=0;break;
case 6: max_value=7;min_value=1;break;
}
if(Flag_add==1)
{
clk_data[clk_number-1]++;
Flag_add=0;
if(clk_data[clk_number-1]>max_value)
clk_data[clk_number-1]=min_value;
}
else if(Flag_sub==1)
{
clk_data[clk_number-1]--;
Flag_sub=0;
if(clk_data[clk_number-1]
-1 ]==0xff)clk_data[clk_number-1]=max_value;
}
if(switch_function==0)
{
if(function_on==0)
{
function_on=1;
FUN=0;
buzzer();
function_state=1;
fun0_flag=1;
set_time[0]=(clk_data[4]/10)*0x10+(clk_data[4]%10);
set_time[1]=(clk_data[3]/10)*0x10+(clk_data[3]%10);
set_time[2]=(clk_data[2]/10)*0x10+(clk_data[2]%10);
set_time[3]=(clk_data[1]/10)*0x10+(clk_data[1]%10);
set_time[4]=(clk_data[5]/10)*0x10+(clk_data[5]%10);
set_time[5]=(clk_data[0]/10)*0x10+(clk_data[0]%10);
}
}
else
{
function_on=0;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
下边是我同事的一个函数,女工程师,女中豪杰,哈哈
//////////////////////////////////////////////////////////////////////////////////////////////////////
void check_switch()
{
if(FUN==0)
{
if(switch_work==0)
{
if(working_on==0)
{
working_on=1;
buzzer();
if(working==1)
{
working=0;
flag0=0;
}
else
{
working=1;
flag0=1;
}
}
}
else
{
working_on=0;
}
if(switch_function==0)
{
if(function_on==0)
{
function_on=1;
buzzer();
program_state=1;
if(function_state==1)
{
function_state=2;
}
else
{
if(function_state==2)
{
function_state=3;
}
else
{
if(function_state==3)
{
function_state=4;
FUN=1;
fun1_flag=1;
working=0;
flagx=1;
}
else
{
function_state=1;
}
}
}
}
}
else
{
function_on=0;
}
if(switch_program==0)
{
if(program_on==0)
{
program_on=1;
buzzer();
program_working=1;
flagv=1;
flagy=1;
flagt=1;
flagw=1;
}
}
else
{
program_on=0;
}
if(switch_up==0)
{
if(up_flag==0)
{
up_flag=1;
up_on=1;
buzzer();
}
}
else
{
up_flag=0;
}
if(switch_down==0)
{
if(down_flag==0)
{
down_flag=1;
down_on=1;
buzzer();
}
}
else
{
down_flag=0;
}
if(switch_AINTSYR==0)
{
if(AINTSYR_on==0)
{
AINTSYR_on=1;
buzzer();
if(AINTSY_state<=63)
{
AINTSY_state=AINTSY_state+1;
}
}
}
else
{
AINTSYR_on=0;
}
if(switch_AINTSYW==0)
{
if(AINTSYW_on==0)
{
AINTSYW_on=1;
buzzer();
if(AINTSY_state>=1)
{
AINTSY_state=AINTSY_state-1;
}
}
}
else
{
AINTSYW_on=0;
}
if(switch_BINTSYR==0)
{
if(BINTSYR_on==0)
{
BINTSYR_on=1;
buzzer();
if(BINTSY_state<=63)
{
BINTSY_state=BINTSY_state+1;
}
}
}
else
{
BINTSYR_on=0;
}
if(switch_BINTSYW==0)
{
if(BINTSYW_on==0)
{
BINTSYW_on=1;
buzzer();
if(BINTSY_state>=1)
{
BINTSY_state=BINTSY_state-1;
}
}
}
else
{
BINTSYW_on=0;
}
}
else
{
text();
}
}
void main()
{
init_working();/*初始化程序*/
start_t0:check_switch();
prepare_work();
if(fun0_flag==1){
ds_settime(set_time);
}
display();
goto start_t0;
}
Wegner将早期高级语言做了分类:
以上内容摘自Grady Booch《面向对象:分析与设计》
1. 所有子程序共用全局数据。
2. 共用数据带来大量交叉耦合。
3. 大量的标志位或者数据定义很难读懂它到底代表什么意思。
早期语言写程序的特点:
既然你发现了它的缺陷,既然你已经知道它的‘错’在哪里,那问题可以得到解决了吧!问题怎么解决,如何改进?别着急!接下来我们将会看语言的下一次革新,还会附上下一阶段的代码做为实例。我简直就是一部活生生的发展史了。
软件固有特性:复杂性等问题
【软件固有的复杂性】
* 问题域的复杂性 :
—— 非功能性需求(可用性,健壮性,成本,性能等)的加入。
—— 开发人员同用户之间沟通的困难。
—— 设计过程中的需求变更。
* 管理开发过程的困难性
* 软件中随处可见的灵活性
—— 这直接导致了软件开发领域不能像其他领域,可以对每个构件给出一个行业标准,比如机械制造行业,软件行业却很少能如此。这导致开发者需要打造大部分模块。
* 描述离散系统行为的问题
—— 无法用连续系统建模的方式,本身也不受物理规律支配。
【复杂系统的5个属性】
* 层次性:可以逐级划分子系统。
* 相对本原:不同观察者对同系统组件的本原性认识不同。
* 分离关注:根据“组件”内部之间关联程度高于“组件”间外部关联程度来划分组件。
* 共同模式:如细胞,管脉系统在动物和植物中都有。
* 稳定的中间形式:复杂系统是演化而来的,曾经的‘复杂系统’或其他‘复杂系统’变成基本组成。
【复杂系统的规范形式】
* 复杂系统的两种构成层次:1. 组成部分(Part of) 2. 是一种(Is a)
在开始分析07年的代码之前,先科普一下:
【普及基础知识】
第二代和第三代前期程序设计语言的程序结构:
这是07年中的一个研发项目,是一个电力抄表终端,下面代码是他的液晶显示屏(128x64)的菜单部分。
代码有点长,先看头文件:
menu.h
//字模数组
extern root_canshu[],root_shuju[],root_zhuangtai[],exit[],zhongduancanshu[],fukongcanshu[],celiangdian0[],
celiangdian0canshu[],celiangdian1canshu[],
celiangdian1[],celiangdian2[],celiangdian3[],zhongduanzhuangtai[],zhuzhantongxin[],fuhekongzhi[]
,tongxincanshu[],gaojingcanshu[],duankoucanshu[],biaoxieyi[],jibencanshu[],gongkongcanshu[]
,diankongcanshu[],dianzijishu[],biaojitongxin[],gongkongfangan[],diankongfangan[],diannengliang[],
xuliang[],shunshiliang[],huanyingshiyong[],celiangdian0shuju[],celiangdian1shuju[],celiangdian2shuju[],celiangdian3shuju[];
//--------------------------------------------------------------------------
//-------------------------------------------
// 子界面编号数组
//-------------------------------------------
unsigned char Son0[1] ={1}; //对应界面0
unsigned char Son1[4] ={2,3,4,0};
unsigned char Son2[5] ={5,6,7,8,1};
unsigned char Son3[5] ={9,10,11,12,1};
unsigned char Son4[5] ={55,56,57,58,1};
unsigned char Son5[5] ={31,32,33,34,2};
unsigned char Son6[5] ={35,36,37,38,2};
unsigned char Son7[3] ={39,40,2};
unsigned char Son8[3] ={41,42,2};
unsigned char Son9[4] ={43,44,45,3};
unsigned char Son10[4] ={46,47,48,3};
unsigned char Son11[4] ={49,50,51,3};
unsigned char Son12[4] ={52,53,54,3};
//*****************
//以下是增加的叶子
//*****************
//-----------------------------------------------------------
//各个界面显示内容指针数组
//-----------------------------------------------------------
unsigned char *menu_char0[1]={huanyingshiyong};
unsigned char *menu_char1[4]={root_canshu,root_shuju,root_zhuangtai,exit}; //各元素枝指向对应行显示内容的字模数组
unsigned char *menu_char2[5]={zhongduancanshu,fukongcanshu,celiangdian0canshu,celiangdian1canshu,exit};
unsigned char *menu_char3[5]={celiangdian0shuju,celiangdian1shuju,celiangdian2shuju,celiangdian3shuju,exit};
unsigned char *menu_char4[5]={zhongduanzhuangtai,zhuzhantongxin,biaojitongxin,fuhekongzhi,exit};
unsigned char *menu_char5[5]={tongxincanshu,gaojingcanshu,duankoucanshu,biaoxieyi,exit};
unsigned char *menu_char6[5]={jibencanshu,gongkongcanshu,diankongcanshu,gongkongfangan,exit};
unsigned char *menu_char7[3]={jibencanshu,gaojingcanshu,exit};
unsigned char *menu_char8[3]={jibencanshu,gaojingcanshu,exit};
unsigned char *menu_char9[4]={diannengliang,xuliang,shunshiliang,exit};
unsigned char *menu_char10[4]={diannengliang,xuliang,shunshiliang,exit};
unsigned char *menu_char11[4]={diannengliang,xuliang,shunshiliang,exit};
unsigned char *menu_char12[4]={diannengliang,xuliang,shunshiliang,exit};
//-----------------------------------------------------------
// 各行文字数量
//-----------------------------------------------------------
unsigned char char_num0[1] = {4};
unsigned char char_num1[4] = {2,2,2,2};
unsigned char char_num2[5] = {4,4,6,6,2};
unsigned char char_num3[5] = {6,6,6,6,2};
unsigned char char_num4[5] = {4,4,4,4,2};
unsigned char char_num5[5] = {4,4,4,3,2};
unsigned char char_num6[5] = {4,4,4,4,2};
unsigned char char_num7[3] = {4,4,2};
unsigned char char_num8[3] = {4,4,2};
unsigned char char_num9[4] = {3,2,3,2};
unsigned char char_num10[4] = {3,2,3,2};
unsigned char char_num11[4] = {3,2,3,2};
unsigned char char_num12[4] = {3,2,3,2};
//-----------------------------------------------------------
struct INTERFACE
{
//当前行号对应子菜单编号,和反显行,对于不需要反显示的无意义
//行号在每次进入新的界面时清零
unsigned char MAX_ROW_NUM; //该界面的最大行数
unsigned char **MENU_char; //指向一指针数组,该数组元素为指向各行显示内容的指针
unsigned char *Son_num; //指向当前界面子界面编号数组的指针
unsigned char *Row_num; //指向当前界面各行文字数量数组的指针
};
struct INTERFACE Windows[13] =
{
{1,menu_char0,Son0,char_num0},
{4,menu_char1,Son1,char_num1},
{5,menu_char2,Son2,char_num2},
{5,menu_char3,Son3,char_num3},
{5,menu_char4,Son4,char_num4},
{5,menu_char5,Son5,char_num5},
{5,menu_char6,Son6,char_num6},
{3,menu_char7,Son7,char_num7},
{3,menu_char8,Son8,char_num8},
{4,menu_char9,Son9,char_num9},
{4,menu_char10,Son10,char_num10},
{4,menu_char11,Son11,char_num11},
{4,menu_char12,Son12,char_num12}
};
//叶子节点的数据结构
struct LEAF
{
unsigned char Father_num; //该叶子要返回的父亲界面号
void (*Display_leaf)(); //指向该叶子的显示函数的指针
};
struct LEAF leaf[28] =
{
{5,display_tongxincanshu}, {5,display_gaojingcanshu},
{5,display_duankoucanshu}, {5,display_biaoxieyi},
{6,display_jibencanshu}, {6,display_gongkongcanshu},
{6,display_diankongcanshu}, {6,display_gongkongfangan},
{7,display_cljibencanshu}, {7,display_clgaojingcanshu},
{8,display_cljibencanshu}, {8,display_clgaojingcanshu},
{9,display_diannengliang}, {9,display_xuliang},
{9,display_shunshiliang}, {10,display_diannengliang},
{10,display_xuliang}, {10,display_shunshiliang},
{11,display_diannengliang}, {11,display_xuliang},
{11,display_shunshiliang}, {12,display_diannengliang},
{12,display_xuliang}, {12,display_shunshiliang},
{4,display_zhongduanzhuangtai}, {4,display_zhuzhantongxinzhuangtai},
{4,display_biaojitongxinzhuangtai},{4,display_fuhekongzhizhuangtai}
};
//-----------------------------------------函数--------------------------------------------------
void Display_char(unsigned char,unsigned char ,unsigned char *,unsigned char);
void Display_row(unsigned char,unsigned char ,unsigned char ,unsigned char *,unsigned char);
void Display_window(unsigned char,unsigned char);
//
//-----------------------------------------函数--------------------------------------------------
void Display_char(unsigned char,unsigned char ,unsigned char *,unsigned char);
void Display_row(unsigned char,unsigned char ,unsigned char ,unsigned char *,unsigned char);
void Display_window(unsigned char,unsigned char);
//-------------------------------------------------------------------------
//功能: 显示16x16汉字
//参数: begneX : 行地址 beginRow:列地址 n: 汉字数量
// s: 显示内容 标志寄存器: 是否反显
//调用: display_Lf() display_Rf()
//zkq 2007.06.04
//-------------------------------------------------------------------------
void display16x16RL(unsigned char beginX,unsigned char beginRow,unsigned char n,unsigned char *s,unsigned char 标志寄存器)
{
unsigned char i;
if(标志寄存器)
{
LCD_Write_ComR(0xb8|beginX);
LCD_Write_ComR(0x40|0);
for(i=0;i<64;i++)
{
LCD_Write_DatR(0xff);
}
LCD_Write_ComL(0xb8|beginX);
LCD_Write_ComL(0x40|0);
for(i=0;i<64;i++)
{
LCD_Write_DatL(0xff);
}
LCD_Write_ComR(0xb8|beginX + 1);
LCD_Write_ComR(0x40|0);
for(i=0;i<64;i++)
{
LCD_Write_DatR(0xff);
}
LCD_Write_ComL(0xb8|beginX + 1);
LCD_Write_ComL(0x40|0);
for(i=0;i<64;i++)
{
LCD_Write_DatL(0xff);
}
}
for(i=0;i
{
displayrow(i,beginX,beginRow+i,s,标志寄存器);
}
}
//--------------------------------------------------------------------------------------
void Display_window(unsigned char win_num,unsigned char sel_row)
{
unsigned char j,f;
if(Windows[win_num].MAX_ROW_NUM < 4)
f = Windows[win_num].MAX_ROW_NUM - 1;
else f = 3;
if(sel_row<4)
{
for(j=0;j<=f;j++)
{
if(j==sel_row)
display16x16RL(2*j,1,*(Windows[win_num].Row_num + j),*(Windows[win_num].MENU_char + j),1);
else
display16x16RL(2*j,1,*(Windows[win_num].Row_num + j),*(Windows[win_num].MENU_char + j),0);
}
}
else if(sel_row<8)
{
for(j=0;j<=Windows[win_num].MAX_ROW_NUM - 5;j++)
{
if(j==sel_row%4)
display16x16RL(2*j,1,*(Windows[win_num].Row_num + j + 4),*(Windows[win_num].MENU_char + j + 4),1);
else
display16x16RL(2*j,1,*(Windows[win_num].Row_num + j + 4),*(Windows[win_num].MENU_char + j + 4),0);
}
}
}
对于接受过自顶而下的结构化程序设计的人来说,首先想到的是用算法分解将复杂系统划分成简单的部分。其中每一个模块是某个总体过程(some overall process)的一个主要步骤(a major step)。下面例子是Booch给出的一个程序的部分设计,完成更新一个主控文件的内容。它完全就是对一个流程的逐级细化。
这样划分的模块虽然可以独立编译,但是完全没有封装的概念,它只关注流程,只是功能的划分。接下来我的实例中你会体会到这一点,当然前辈的功力还是很深厚的,程序一直很稳定,但是阅读起来很费劲,扩展性不好,当然像TCP部分也做了一定的封装。整体上主要还是按照功能而不是实体做为模块划分的标准。即使如此,我从中还是收益匪浅的。
接下我会把代码的各个功能模块做些介绍,期间插入我自己的理解,最后根据分析结果做些总结。后边的代码没有那么简单了,加上多年没有看过,以后就不再设什么前提。温故而知新,很多新发现也是出乎我意料的,因为今天的眼光不同以前。大家有什么意见和观点尽管提,感兴趣的话一起开始发现之旅吧。
首先从内容上对这个程序做个大概的浏览:
所有代码:
C文件:
makefile:
int main(void)
{
.......
init_system(); //系统初始化
InitPassiveSock(); //监听套接字初始化
pthread_create(&ReceID, NULL, (void *)&RecePross, NULL); //创建接收处理线程
while(1){
QuerySocketsMsg(); //处理socket
.......
}
.......
}
void init_system(void)
{
init_para(); // 1
init_serial(); // 2
init_ethernet(); // 3
......
}
开始之前,先介绍一下这个程序最主要的几个数据结构:
/* 套接字结构 */
typedef struct Sockets {
fd_set readfds, writefds, exceptfds; // 1. 要被检测的可读、可写、例外的套接字集合
int PmuSock; // 2. PMU规范套接字
......
struct Sockets_Comm *Comm; // 3. 为设置中每一个通讯通道建立网络接口
} Sockets;
2. 用来监听(listen())主站链接的监听socket。
3. 是个结构体数组:数组长度等于通信通道数量。结构体定义如下:
/* COMM网络结构 */
typedef struct Sockets_Comm{
int Protocol; // 1. 该链接使用的规约类型
int MainSock; // 2. 主套接字(即上面提到的监听套接字accept到的链接套接字)
pthread_t MainThreadID; // 4. 主线程ID,该线程用于处理该链接通信。
pthread_cond_t MainCond;
pthread_mutex_t MainMutex;
............
}Sockets_Comm;
2. (如上注释)
3. (如上注释)
在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”[1],这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用质量的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。
开闭原则的命名被应用在两种方式上。这两种方式都使用了继承来解决明显的困境,但是它们的目的,技术以及结果是不同的。
勃兰特·梅耶一般被认为是最早提出开闭原则这一术语的人,[来源请求]在他1988年发行的《面向对象软件构造》中给出。这一想法认为一旦完成,一个类的实现只应该因错误而修改,新的或者改变的特性应该通过新建不同的类实现。新建的类可以通过继承的方式来重用原类的代码。衍生的子类可以或不可以拥有和原类相同的接口。
梅耶的定义提倡实现继承。具体实现可以通过继承方式来重用,但是接口规格不必如此。已存在的实现对于修改是封闭的,但是新的实现不必实现原有的接口。
多态开闭原则
在20世纪90年代,开闭原则被广泛的重新定义由于抽象化接口的使用,在这中间实现可以被改变,多种实现可以被创建,并且多态化的替换不同的实现。
相比梅耶的使用方式,多态开闭原则的定义倡导对抽象基类的继承。接口规约可以通过继承来重用,但是实现不必重用。已存在的接口对于修改是封闭的,并且新的实现必须,至少,实现那个接口。
罗伯特·C·马丁1996年发表的文章《开闭原则》[2]是使用这种方法的启发式著作。在2001年,Craig Larman把开闭原则关联到了Alistair Cockburn的名为受护的变量的模式以及David Parnas关于信息隐藏的讨论。[3]
以上内容来源于《维基百科》,总得来说,开闭原则不仅针对面向对象设计,即使面向过程设计的软件,如果尽可能做到这一点也是很有必要的。虽然面向过程的先天性决定了,这是困难的。然而有些规模庞大的软件:如linux内核,虽然它本身具备面向对象思想,终究不是一个完全面向对象的大工程,但是它的开闭原则做得很到位,这个了解的人一定深有体会。
曾经看到过Linus在一个帖子里痛批了面向对象语言。他认为面向对象语言以对象为核心,加一些相关联的方法,简直是呓语。重要的东西应该是数据结构,对象本身有啥重要?真正有意思的,是在不同类型的不同对象交互而且有锁规则的时候。但是,即使是这时候,封装什么“对象接口”也绝对错误,因为不再是单一对象的问题了。他的结论是,面向对象解决的都是一些小问题。
确实有很多“大人物”一直强调:“最重要的是数据结构”。
关于这个问题,我最近几天在考虑。一个基本的现象是:目前几乎所有的操作系统都不是以对象为核心的,就拿大家熟悉的ucos来说,它最核心的是:
中断的管理 ------
内存的管理 ------ 内存控制块
任务间的通信和同步等 ------ 消息队列,互斥量,邮箱等
无不体现了数据结构的重要,这里面几乎看不到对象的存在。
到底对象的适用范围在哪里,我们该怎么做?其实这是一个很有挑战性的问题,目前还没有看到过专门讨论这个问题的论著。首先我们大致可以看到:所有“平台部分”代码(像操作系统)最好不要以对象为中心,在没有使用操作系统的应用里,对CPU的管理:中断的管理,并发的实现,消息和同步机制等也属于这个范畴,这部分的特点是:
2. 虽然CPU平台有差异,但是体系结构相同或相似,处理方法大部分相同(否则就不存在跨平台的OS)。
3. 这部分是同CPU打交道的,和人类的思维差别很大。
2. 怎么划分两部分的界线。
3. 怎么才能最有效的结合两部分,有没有一套完整统一的方法。
4. 两部分各自的实现问题。
其中第4个问题是我们千百本书里面介绍有的。要这样,最近一直在想这个问题:
Soul:我在写书讨论“面向对象的局限性”
我 :En.这个倒与我的意见一致。哈哈哈。
我 :“绝对可以用面向过程的方法来实现任意复杂的系统。要知道,航天飞机也是在面向过程的时代上的天。但是,为了使一切变得不是那么复杂,还是出现了‘面向对象程序设计’的方法。”
我 :——哈,我那本书里,在“面向对象”一部分前的引文中。就是这样写的。
Soul:现在的程序是按照冯。诺伊曼的第一种方案做的,本来就是顺序的,而不是同步的。CPU怎么说都是一条指令一条指令执行的。
我 :面向过程是对“流程”、“结构”和“编程方法”的高度概括。而面向对象本身只解决了“结构”和“编程方法”的问题,而并没有对“流程”加以改造。
Soul:确实如此。确实如此。
我 :对流程进一步概括的,是“事件驱动”程序模型。而这个模型不是OO提出的,而是Windows的消息系统内置的。所以,现在很多人迷惑于“对象”和“事件”,试图通过OO来解决一切的想法原本就是很可笑的。
Soul:我先停下来,和你讨论这个问题,顺便补充到书里去。
我 :如果要了解事件驱动的本质,就应该追溯到Windows内核。这样就涉及到线程、进程和窗体消息系统这些与OO无关的内容。所以,整个RAD的编程模型是OO与OS一起构建的。现在很多的开发人员只知其OO的外表,而看不到OS的内核,所以也就总是难以提高。
Soul:OO里面我觉得事件的概念是很牵强的,因为真正的对象之间是相互作用,就好像作用力和反作用力,不会有个“顺序”的延时。
我 :应该留意到,整个的“事件”模型都是以“记录”和“消息”的方式来传递的。也就是说,事件模型停留在“面向过程”编程时代使用的“数据结构”的层面上。因此,也就不难明白,使用/不使用OO都能写Windows程序。
我 :因为流程还是在“面向过程”时代。
Soul:所以所谓的面向对象的事件还是“顺序”的。所以我们经常要考虑一个事件发生后对其他过程的影响,所以面向对象现在而言是牵强的。
我 :如果你深入OS来看SEH,来看Messages,就知道这些东西原本就不是为了“面向对象”而准备的。面向对象封装了这些,却无法改造它们的流程和内核。因为OO的抽象层面并不是这个。
我 :事件的连续性并不是某种编程方法或者程序逻辑结构所决定的。正如你前面所说的,那是CPU决定的事。
Soul:比如条件选择,其实也可以用一种对象来实现,而事实没有。这个是因为cpu的特性和面向对象太麻烦。
我 :可能,将CPU做成面向对象的可能还是比较难于想象和理解。所以MS才启动.NET Framework。我不认为.NET在面向对象方法上有什么超越,也不认为它的FCL库会有什么奇特的地方。——除了它们足够庞大。但是我认为,如果有一天OS也是用.NET Framework来编写的,OS一级的消息系统、异常机制、线程机制等等都是.NET的,都是面向对象的。那么,在这个基础上,将“事件驱动”并入OO层面的模型,才有可能。
Soul:所以我发觉面向对象的思维第一不可能彻底,第二只能用在总体分析层上。在很多时候,实质上我们只是把一个顺序的流程折叠成对象。
我 :倒也不是不可能彻底。有绝对OO的模型,这样的模型我见过。哈哈~~但说实在的,我觉得小应用用“绝对OO”的方式来编写,有失“应用”的本意。我们做东西只是要“用”,而不是研究它用的是什么模型。所以,“Hello World”也用OO方式实现,原本就只是出现在教科书中的Sample罢了。哈哈。
Soul:还有不可能用彻底的面向对象方法来表达世界。 因为这个世界不是面向对象的。 是关系网络图,面向对象只是树,只能片面的表达世界。所以很多时候面向对象去解决问题会非常痛苦。所以编程退到数据结构更合理,哈哈。
我 :如果内存是“层状存取”的,那么我们的“数据结构”就可以基于多层来形成“多层数据结构”体系。如果内存是“树状存取”的,那么我们当然可以用“树”的方式来存取。——可惜我们只有顺序存取的内存。
我 :程序=数据+算法 ——这个是面向过程时代的事。 程序=数据+算法+方法 ——在OO时代,我们看到了事件驱动和模型驱动,所以出现了“方法”问题。
Soul:我的经验是:总体结构->面向对象,关系->数据结构,实现->算法
Soul:看来我们对面向对象的认识还是比较一致的。
1. 编程技巧:可移植性问题、模块化,C语言问题等等。
2. 功能实现:菜单的实现问题、GUI
3. 工具的使用:keil,gcc,uml等。
4. 外设驱动:显示屏,can,输入设备等。
5. 功能模块:加密,校验,滤波等等。
6. 处理器相关:中断处理技巧,寄存器操作,定时器等等。
STC16是开发者的噩梦?
少写点if-else吧,它的效率有多低你知道吗?
早期MCU芯片是怎么加密的?
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!