建立编程机制





        对于系统而言,机制就是系统设计的程序;对于单个问题而言,机制就

	是解决问题的方法摸板。本文以实例来说明机制是如何建立和运作的。(机

	制也可以称为模式,不过机制略多带一些管理控制方面的内容)



  以前的编程过程中,虽然是按模块划分,或按对象实现封装,但从来没有考虑

过建立问题解决机制这么一个概念。由于这次的软件项目大了,开发人员好几个,

不再是一个人的事情,所以,不得不考虑多人协同开发的方法。

  要想保证质量和进度地协同开发,将面对以下几个困难:

  

1)开发者的水平和开发习惯不一样,如何在统一思想的同时发挥个人能力;

2)系统的需求分析和设计不可能一步到位,调整系统设计后对每个开发者的影

响很难估计,有时可能需要重做。如何降低调整系统设计对部分内容的影响程度;

3)开发人员的流动不可避免,技术资源的延续性如何得以保持仅仅靠详细的文

档,而不考虑程序自身的可读性和可延续性是不够的(让新手了解每一个变量

或函数的功能仍是很困难的)。

4)当时间进度已经临近或者技术难度太大而要求简化系统功能时,可能出现某

些部分的开发已经要求另一部分必须完成的矛盾,如何避免此类骑虎难下的局面。

  此时,机制的建立就十分有必要了。这是一种编程标准的制定,这种标准

不仅仅是变量、函数等命名规则,更多的是许多设计和编写程序处理问题的法

则和规定。

  对于系统而言,机制就是系统设计的程序;对于单个问题而言,机制就是解

决问题的方法摸板。



  这里有一个简单而有效的办法:

步骤一:统一开发群体的基本开发思想(即基本开发机制)

1)无论水平高低,每位成员必须以学习态度来到这里共同开发

  对于任何一个未开发的项目,我们都是学生,即便是第二次开发此类项目

,同样有待学习。在一起开发的团体,首先要解决的是思路定式。开发经验和

惯例并不是定论,创新的东西或许更有价值。所以,端正学习的态度来开发,

无疑是重新审视已有技术和创新新的技术的保证。2)不是在为自己开发程序,

而是在为后人开发程序

  这句话的意思是:你开发的那部分程序就是一个完整的产品,它的用户就

是后来的开发者。所以,你必须考虑你的产品如何简单明了地交给其他开发者

继续开发或者扩展功能。

3)没有抽象出问题解决公式不要写代码

  如果是接到任务就不管3721地按需求写出一堆代码,任务虽然是完成

了,但这段代码也许跟着就废了:增加或修改功能等于重做一次软件。任务虽

然是明确的,但任务中各项需求之间的关系和有可能更改需求的处理却是不太

明确的,这需要综合分析。请看一个简单的例子:

  CAD图形设计中点、线、圆的绘制编辑和数据管理,要求有绘制、点选

、删除、移动、修改功能。以下设计是完全根据需求实现:

  #define dotID 1

  #define lineID 2

  #define CircleID 3



  struct TDot {

    long X,Y; 

  };

  struct TLine {

    TDot start;

    TDot end;

  };

  struct TCircle {

    TDot center;

    long radius;

  };

  struct TData {

    1=点 2=线 3=园

    long typeID;    file://区别数据类型,1void *data;

  };

 class TPaper (

    TData *dataVal;

    void Draw(TData *data) {

      switch(data->typeID)

      {

      case 1:

        file://画点

      case 2:

        file://画线

      case 3:

        file://画圆

      default:

      }

    }



    void Delete();

    void Move();

    bool Select(long x,long y);

    Update();

  };

  ......

  

  抽象各类图形元素的设计:将点、线、圆的共性部分抽象成一个基类,使用虚

拟函数,让各自图素各自解决画法和操作。



  typedef MyDataType long



  struct RPoint {

    MyDataType X,Y;

  }

  class TLink {

    file://数据指针链定义;

  }

  class TPaper : public TLink {

    virtual void Draw() {

      TPaper *temp;

      temp=FisrtData();

      while(temp不到结束) {

        temp->Draw();

        temp=temp->NextData;

      }

    }

    virtual void Delete();

    virtual void Move();

    virtual bool Select(long x,long y);

    virtual Update();

  };

  class TDot : public TPaper {

    RPoint crood;

    void Draw() {

      表示点。

file://以crood.X,crood.Y画一个十字交叉    }

    void Delete();

    void Move();

    bool Select(long x,long y);

    Update();

  }

  class TLine : public TPaper {

    RPoint start;

    RPoint end;

    void Draw() {

      (end.X,end.Y)画一条线。

file://以(start.X,start.Y)到    }

    void Delete();

    void Move();

    bool Select(long x,long y);

    Update();

  }

  class TCircle : public TPaper {

    RPoint center;

    MyLengthType radius;

    void Draw() {

      径为radius画一个圆。

file://以(center.X,center.Y)为圆心,半    }

    void Delete();

    void Move();

    bool Select(long x,long y);

    Update();

  }

  ......



  两段程序的区别:第一段,初期思路清晰,但,当增加画圆弧或者二次曲线等时,

程序的代码将改动的地方多,凡需要区分点、线、圆、以及其他的类别时,必须通过

switch语句检查当前应该显示的类型数据。这样导致增加功能或者变更画法操作时,

许多地方都要更改。第二段,初期设计时较难,经过分析设计后,所写代码有很好的

扩展性。增加一个圆弧,也仅仅只增加一个圆弧的类即可,而且许多操作都封装到一

个类中,不会对外部程序体没有太大的影响。如果再增添一个注册机制,将新增内容

注册到TPaper中,这样扩展功能更是易如反掌,并且还保证了系统的可靠性。

  如果觉得此方法的类占用了太多的内存空间(虚拟函数在类实例中需要空间),

可以建立一个基本结构:

  struct RDataBase {

    TPaper *type;

    RDataBase *nextData;

    void Add(RDataBase *);

    void Remove(RDataBase *);

  }

  struct RDot : public RDataBase{

    MyDataType X,Y;

  }

  struct RLine : public RDataBase {

    RPoint start,end;

  }

  struct RCircle : public RDataBase {

    RPoint center;

    MyDataType radius;

  }

  将图素的数据分别实现为一个结构,将各自图素类的一个实例指针置入一个TPaper *typ

e指针变量中,Paper的Draw()实现为:

    virtual void Draw() {

      TPaper *temp;

      RDataBase *tempData;



      tempData=FisrtData();

      while(tempData不到结束) {

        temp=tempData->type;

        temp->Draw();

        tempData=tempData->nextData;

      }

    }

  经过一系列的问题抽象形成可确定的公式后,即便程序没有实现太多的功能,

我们可以认为它已经完成了90%的工作。因为,所有需要增加的内容已经被机制化,

只要按照这样一个明了的机制去实现圆弧、二次曲线、矩形框、多边形、双线等,不

需要了解太多,其他开发者都可以很快加入它的工作。

  其实,面向对象的设计方法就是一种机制的建立,不过,面向对象偏重于类型的

建立,注重封装,而机制概念则不仅考虑类的建立,同时还考虑非类部分的建立,即

使程序处理得非常没有结构化,它的机制同样存在。一个类封装的不是非常好但机制

确实很不错的例子:

  上篇文章提到过的通用编辑器,为了实现编辑器摸板化,我们尽量将编辑器的共

性设计在内部,而且让扩展者独立创建编辑器能够识别的数据类(没有任何派生继承

)。所以,采用了一系列的接口函数。其中,显示接口函数更具有机制性:

  struct RData {     file://扩展者自己定义

    long ID;   file://除零外的所有整数,由    

    char *data;  file://可以是文本,也可以是图形、表格或控件



    TFont *font;

  }

  当前需要显示的内容

  file://void *GetWord() file://是一个接口函数,返回  

  file://long UserTextRect(text,X,Y) file://是接口函数,返回特显内容的宽度  

  ShowText(long &X,long &Y)   file://显示一行的内容  

  {  RData *text;

    text=(RData *)GetWord();

    while(text!=NULL) {

      if(text->ID==0)

        作为文本,内部显示

        X+=MyTextOut(text,X,Y);  //



      else

        作为特显,外部显示

        X+=UserDataOut(text,X,Y); //



      text=(RData *)GetWord();

    }

  }

  

  这样的机制,外部只需要在当前位置显示应该显示的内容,比如图片。(当然,

仅仅这样的数据信息还不够,至少还要行高。但,这样的机制是合理存在的。其他

的数据管理机制不详写了)。在这个例子中,并不要求外部的扩展者有基类的限制

,他仅仅提供接口函数即可。



步骤二:根据步骤一的基本开发机制,要求开发者提供两份文档:程序说明文档和

程序使用文档。程序说明和使用文档同时留给项目管理者,程序使用文档交给其他

参与此部分程序的开发者。保证核心技术的保密同时提供可供扩展完善的机制。项

目管理者还可以根据进度要求和资金实力,临时招聘新的开发者,经过短期的培训

后,迅速加入开发行列,从而加快完成产品的进度。



步骤三:定期阅读程序使用文档,检查机制是否具有包容性、灵活性。检查具体办法:

1)机制能够适应多少种情况,常见情况是否全包容,特殊情况考虑了多少?

2)如果考虑了特殊情况,那么不存在特殊情况时机制是否有效、效率如何?

3)目前没有考虑的情况,以后增加是否容易;如果不容易,是否简单调整机制后能够解决?

4)机制的理解是否容易,它的要求是否很多或者很难控制。

  经过步骤三的检查,可最大程度地避免了系统更改(或简化)后,对某些部分

之间产生的逻辑矛盾。



步骤四:精益求精,机制的建立不能将就,必须经常性反思机制的机能合理程度,

随时修改完善。特别是才开始创建机制时,对机制要求更加细腻周全。



  下面一个CAD命令行处理机制的例子:



  定义:命令行只执行文本命令和提示信息。菜单、工具条、按钮、热键等均

向命令行发送命令和数据。命令行运作图:(以画圆为例)

  





画圆指令    命令行  

    等待输入命令  

    |  

    输入命令"c"  

    |  

画圆程序开始  〈===============  调用相应的画圆程序  

发送提示出选项  ================〉  等待选择选项  

    |  

是圆心、半径圆  〈===============  传送给画圆所选"r"  

发送等待输入半径命令的项"r"  ================〉   等待输入半径  

    |  

画圆  〈===============  得到半径  

画圆结束  ================〉  等待输入命令  

      



  

  交互式一问一答,将画圆的所需参数一一输入完后,画圆。命令行从最开

始就是等待输入命令的状态,当输入命令后,命令行根据输入命令调用相应的

命令执行程序;在执行程序中,如果需要更多的参数信息,将向命令行发送选

项或需要输入的命令,控制权有交给了命令行;命令行等待选择或输入参数后,

将所的结果返回到执行程序中,执行程序如果还需要信息,继续重复与命令行的

交互传送工作直到可以执行程序命令为止;执行完命令后,又将控制权交给命令

行;命令行回到等待输入命令。

  通过命令行这样的操作,我们可以编写一些命令批处理文本,当在命令行

中粘贴这些批处理文本后,命令行依次执行命令。命令行支持"\" 缺省输入

的控制等高级扩展功能。

  有了命令行的机制,实现其他图素画法变得如同文本操作一样简单。



  总之,如果设计出良好得机制,可以说就获得有了可靠得核心程序。



梦郎







返回主页