上一节,我们提出如何用C做大程序的问题


编程追求的是高内聚,低耦合。这个大家都会说,但是怎么做至今没有通用的定律。

在结构化编程中,将程序模块化,然后定义清晰的接口去使模块协作,这是高质量结构化编程的第一步。

 

但这一步已经是相当高深的一步(我在这步现在还只能说是水平参差的阶段。。)——系统分析设计的精华其实也在这里。我们关注于如何设计接口,使其降低模块之间的耦合。设计糟糕的接口将会是糟糕程序的开端



在C语言中,接口是通过函数的形式提供的。
一个典型的C语言模块可能是这样:
//头文件
void interface_a();

//c文件
static int local_var_a;

static implement_a(){
}

static implement_b(){
}

void interface_a(){

//调用implement_a, implement_b

}
其中只有interface_a是对外公开的函数,implement_a和implement_b是模块内部使用的函数,由于static的限制,其他文件不能使用这两个函数,类似private,同理变量local_var_a也成了模块内部的全局变量,因此这些都属于模块的实现,是对外不可见的。因此整个模块只通过interface_a对外暴露
不过怎么用C语言写出来不是我们的重点。

怎么设计对外的interface,到底要暴露多少东西,一个模块有多大,包含多少功能

这些是模块设计的重中之重。

我们可以参考以下几个准则(参考《Unix编程艺术》):
简洁性 - 合理的暴露接口,不要公开多余的信息。注意简洁和接口数量少时不等价的。
正交性 - 修改一个接口不会或很少导致另一个接口的改变。这也是为什么说简洁和数量少是不等价的

 

例如这个例子

//投掷骰子游戏中Dice(骰子)类中一个方法的设计:投掷骰子,获取结果
int getRollValue(){
  int rollValue = rand();
  return rollValue;
}
//另一种设计
static int rollValue;
void roll(){
  rollValue = rand();
}
int getRollValue(){
  return rollValue;
}
哪种好呢?
我的答案:后者
前者虽然接口数量少,但是后者更满足正交性,它遵循了一个原则——“命令与查询分离”,或者说“可变与不可变分离“。roll是一个命令操作,会产生变化;而getRollValue则是一个查询(从名字看起来也应该是这样),那么它最好不要带来会影响外部的变化。

更重要的是,前者隐藏了一个潜规则:每用这个函数获取一次结果,事实上却是进行了一次投掷骰子的操作。如果在同一个回合调用这个函数两次,将会造成错误。这在接口名称上是没有体现的。

接口语义应该是明确的——调用者觉得接口做了什么,那么事实上也应该做了什么。在我们隐藏实现的同时不能带来潜在的会影响外界的变动,否则结果将使调用者无法正常使用而强迫他们了解接口内部实现的东西,同时也产生耦合而导致接口修改困难,这不是通过注释或者文档就能解决的问题。

 

上面的例子看起来似乎不够过瘾,因为它只涉及了一个函数的设计,也仅仅说明了一个小小的规则。

事实上接口设计可以够我写一本书,更多的设计原则可以看看各种设计指导书籍(看后面),我没必要机械的介绍了。我写的东西主要是起启发的作用,剩下的还是要大家多看多写。


有接口,当然就先要有模块,事实接口是在模块设计之后的事情。后面我将会拿出更实战派的例子,来看看模块跟接口设计的关系。慢慢的我也会弱化结构化,C/C++这些东西——我们的目标是要写好程序,而不是会某种模式。

 

待续--

 

 

recommend resource:

 

面向对象的7个原则以及GOF设计模式

开闭原则,Liskov替换原则,依赖倒转原则,接口隔离原则,组合、聚合复用原则,Demeter法则

看了一下C语言,不妨回过头来看看面向对象的一些设计原则,还有经典的GOF(四人帮)设计模式。全部都能google到,或者参考《敏捷软件开发:原则,模式与实践》

你会渐渐发现这些原则其实也能与结构化编程融会贯通

 

《Unix编程艺术》

对Unix系程序设计经验做了充分的总结,其中包括模块设计的部分。作者似乎不太支持OO编程,不过这本书可以加深对结构化编程的理解。

posted on 2009-06-02 21:57  中大信息中心数媒部  阅读(665)  评论(1)    收藏  举报