软件开发>杂文>4

共享软件开发的点滴心得

 [中文][English][日本語

 [机器人制作] [软件开发] [豆知识] [Squeak] [关于]

刀耕火种的繁荣时代

刘新宇

2006年三月

 

在以前的一篇 文章《资源管理的变法》中,我曾经为了读者理解方便,刻意把宋朝说成自然环境恶劣,生产落后的“刀耕火种”时代。这其实是胡言乱语的,不可当真。实际上宋 朝可以说是我国古代极其繁荣的一个顶点。经过了宋朝连年战乱后的元朝,其繁荣和文明的程度仍然让马可波罗感到震惊。在他的游记中即使除去关于元大都的叙 述,对于南宋故都杭州的描写,也仍然让人惊讶不已。

 

问题就是在于,在我们现代人看来的“刀耕火种”时代,没有电,没有石油,没有汽车,没有空调和抽水马桶,文明和繁盛是如何实现的呢?

 

如果说今天软件开发界的文明和繁盛中,面向对象是一个代表;C++Java这些是实现繁荣的电,石油和汽车。那么在“刀耕火种”的时代,只有ANSI C这个冷兵器,如何实现“盛世”呢?

 

首先要看一看盛世的特点,比如汉代的“文景之治”,唐代的“贞观之治”“开元盛世”。在物质丰富的同时,政治也比较宽松,文化得到发展。这些都可以归纳成一些特点。在软件开发领域,面向对象也有一些特点,最主要的是如下三个:

  • 封装性
  • 继承性
  • 多态性

 

按照一贯的风格,为了方便读者理解,这里按部就班,把上述三个特性类比为文明社会的3个特点:

  • 封装性——政治组织制度
  • 继承性——人才选拔制度
  • 多态性——财政税收制度

 

实现手法上,做这样的类比:

  • C++的实现手法——现代社会的实现手法
  • 的实现手法——古代社会的实现手法

 

最后本文再给出在不支持多线程的操作系统上实现多线程的手法。

 

首先看文明社会中的政治组织制度。政治组织中非常重要的一点就是“按部就班,各司其职”。同时官员和职能也明确划分,共同“封装”在指定的部门里。对应于面相对象的封装性,其特征也是如此。这一特性把算法和数据结构等在概念上属于同一事物的内容,统一包装起来,例如C++中:

 

class Encapsulate{

public:

     void setValue(const int v){value=v;}

     const int  getValue() const {return value;}

private:

     int value;

};

包装内部既有数据value,也有方法如setValuegetValue,这些方法可以按照非常明确的从属关系加以调用,例如:

int main(int argc, char* argv[]){

Encapsulate v;

v.setValue(3);

cout<<"value = "<<v.getValue()<<"\n";

//error cout<<v.value;

reuturn 0;

}

 

政府分门别类,官员按部就班。现代文明社会可以,汉代的“九品中正”,唐代的“三省六部”也行。用ANSI C实现封装性的思路是使用函数指针。由于在ANSI C中,结构体的含义类似C++Java中的类。虽然C中的结构体不允许含有函数成员,但是函数指针可以被当作普通的数据成员放入结构体中。唯一要做的就是,一定要正确初始化这些函数指针。所以ANSI C实现的封装特性如下:

 

首先在头文件中:

struct Encapsulate{

     int value;

     void (*setValue)(struct Encapsulate* p, const int v);

     const int (*getValue)(struct Encapsulate* p);

};

struct Encapsulate* create();

然后在C文件中:

static void setValue(struct Encapsulate* p, const int v){

     p->value=v;

}

static const int  getValue(struct Encapsulate* p){

     return p->value;

}

struct Encapsulate* create(){

     Encapsulate* p=(Encapsulate*)malloc(sizeof(Encapsulate));

     p->setValue=setValue;

     p->getValue=getValue;

     return p;

}

现在可以看看封装的效果:

int main(int argc, char* argv[]){

Encapsulate* e=create();

     e->setValue(e, 3);

     printf("value = %d\n", e->getValue(e));

     free(e);

reuturn 0;

}

不仅输出和前面一摸一样,而且在调用方式上也非常相似。如果为了理解方便,还可以定义一个self变量:

struct Encapsulate{

     //

     struct Encapsulate* self;

};

在初始化create时:

struct Encapsulate* create(){

     Encapsulate* p=(Encapsulate*)malloc(sizeof(Encapsulate));

     p->self = p;

     p->setValue=setValue;

     p->getValue=getValue;

     return p;

}

 

这样调用的时候,就可以按照这个形式:

e->setValue(e->self, 3);

 

人才选拔和培养是现代文明社会中非常重要的一环,通过人才的培养和选拔,社会中沉淀的知识和经验得以积累,前人的成果得以被后人“继承”。继承性是“文明中”代码重用的重要基础之一。在C++中,子类从父类继承,可以获得父类的大量特性,例如:

class Base{

public:

     void setValue(int v){value=v;}

     int getValue(){return value;}

     int value;

};

class Derived:public Base{

public:

     void increase(){value++;}

     void decrease(){value--;}

     int key;

};

子类通过继承,可以在具有父类所有特性的同时,再增加子类特殊的内容,而原来针对父类的操作,也可以针对子类而不必重新改动,例如:

void initBase(Base* p){

     p->setValue(0);

}

void display(Base* p){

     cout<<"value="<<p->getValue()<<"\n";

}

 

int main(int argc, char* argv[]){

     Derived foo;

     initBase(&foo);

     foo.increase();

     foo.increase();

     display(&foo);

     foo.decrease();

     display(&foo);

     foo.key=3;

}

现代社会通过社会化的教育体系实现的功能,在古代社会同样可以实现,春秋时孔子就搞“民间教育”,弟子三千,贤者七十有二。在选拔上,现代社会有各种考试,在汉代有“举孝廉和举秀才”,唐代开始有国家统一的考试。因此继承性不是C++Java的专利,也可以通过ANSI C加以实现,思路是在子结构体的起始位置,放入父结构体的一段数据[3]。这样只要将子结构体强制类型转换为父结构体后,就可以直接被直接当作父结构体操作。这样操作不会出现问题的原因在于,那些针对父结构体的函数,只会访问子结构体数据的前面一段,而不会访问到后继的数据部分。这个思路用ANSI C实现如下:

首先在头文件中这样定义:

struct Base{

     void (*setValue)(struct Base* self, int v);

     int  (*getValue)(struct Base* self);

     int value;

};

void constructBase(struct Base* p);

struct Derived{

     struct Base base;  /*实现继承*/

     void (*increase)(struct Derived* self);

     void (*decrease)(struct Derived* self);

     int key;

};

void constructDerived(struct Derived* p);

void initBase(struct Base* p);

void display(struct Base* p);

 

由于C中没有构造函数的概念,所以constructXXX负责了一部分的任务,在C文件中,上述定义的实现如下:

 

/* Base member functions */

static void setValue(struct Base* self, int v){

     self->value=v;

}

static int getValue(struct Base* self){

     return self->value;

}

void constructBase(struct Base* p){

     p->setValue=setValue;

     p->getValue=getValue;

}

/* Derived member functions */

static void increase(struct Derived* self){

     self->base.value++; /*或者((struct Base*)self)->value++;*/

}

static void decrease(struct Derived* self){

     self->base.value--; /*或者((struct Base*)self)->value--;*/

}

void constructDerived(struct Derived* p){

     constructBase((struct Base*)p); /*调用父类构造函数*/

     p->increase=increase;

     p->decrease=decrease;

}

/* global functions */

void initBase(struct Base* p){

     p->setValue(p, 0);

}

void display(struct Base* p){

     printf("value=%d\n", p->getValue(p));

}

上面代码中,最体现特点的是带有汉字的注释部分,这些都表明,子结构一旦向上转换为父结构后,就可以被当作父结构来对待。这是面向对象中继承的英文解释:is a type of的良好诠释。下面可以测试一下:

 

int main(int argc, char* argv[]){

     struct Derived foo;

     constructDerived(&foo);

     initBase((struct Base*)&foo);

     foo.increase(&foo);

     foo.increase(&foo);

     display((struct Base*)&foo);

     foo.decrease(&foo);

     display((struct Base*)&foo);

     foo.key=3;

     return 0;

}

调用方式和前面C++的基本一致,输出结果也一摸一样。

文明社会的政府为了保证其财政收入,都拥有一套财政税收制度。基本含义不变而课税方法多样,有的关注生产环节,有的关注流通环节,还有的着眼在消费。这样的“多态性”可以保证提纲挈领,纲举目张。在C++中多态性(这里是指运行时的多态,实现编译期多态的模板技术暂不讨论)通过虚函数(virtual function)override/overwrite进行实现(这里暂且不提overload)。例如:

class Point{

public:

     Point(int _x, int _y):x(_x), y(_y){};

     virtual double radius(){ return sqrt(double(x*x+y*y)); }

     int x;

     int y;

};

class Point3D:public Point{

public:

     Point3D(int _x, int _y, int _z):Point(_x, _y), z(_z){};

     double radius(){return sqrt(double(x*x+y*y+z*z)); }

     int z;

};

int main(int argc, char* argv[]){

     Point* p=new Point(1,1);

     cout<<"radius ="<<p->radius()<<"\n";

     delete p;

     p= new Point3D(1,1,2);

     cout<<"radius ="<<p->radius()<<"\n";

     delete p;

}

在这个例子里,Point定义了平面上的点,而Point3D定义了空间上的点,当计算点到原点的距离radius的时候,平面的计算方法和空间的计算方法不同,在这里多态的表现就是,子类Point3D重新实现了父类Point的计算方法,而其他内容则采取“拿来主义”一并继承过来。

虽然现代社会拥有完善的各种课税,比如生产和销售环节的增值税,建筑服务行业的营业税,针对企业和个人的所得税,其根本是提供国家的财政收入。在中国古代,春秋时期鲁国在前594年即实行了“初税亩”,按照土地向私有者课征。此后各朝变迁变法,税收都是其中重要一环。用ANSI C实现多态的方法如下:

首先在头文件中定义普通平面上的点和三维空间的点,并按照前面的做法,建立继承关系,为了说明多态的灵活性,还增加了一个彩色平面点的定义。:

struct point{

     double (*radius)( struct point* self);

     void   (*display)(struct point* self, const char* name);

     int x;

     int y;

};

struct point* create_point(int x, int y);

struct point3D{

     struct point super;

     int z;

};

struct point* create_point3D(int x, int y, int z);

enum color_t{RED, GREEN, BLUE};

struct color_point{

     struct point super;

     enum color_t color;

};

struct point* create_color_point(int x, int y, enum color_t color);

在这套继承体系中,三维空间点和彩色平面点都从普通平面点继承来,但是在使用虚函数实现多态上有一些区别。三种点各自实现显示信息的方法display,但是彩色点直接使用平面点的radius方法计算到原点的距离,而三维空间点则override这个计算的方法。具体的实现如下:

/* point in 2D plan default member function */

static double radius(struct point* self){

     return sqrt((double)(self->x*self->x + self->y*self->y));

}

static void display(struct point* self, const char* name){

     printf("point %s: x=%d, y=%d\n", name, self->x, self->y);

}

static void init_point(struct point* p, int x, int y){

     p->x=x;

     p->y=y;

     p->radius=radius;

     p->display=display;

}

/* point ctor */

struct point* create_point(int x, int y){

     struct point* p=(struct point*)malloc(sizeof(struct point));

     init_point(p, x, y);

     return p;

}

普通平面点得以实现后,现在实现三维点,需要override的方法包括radiusdisplay,另外在构造时还要额外初始化z成员:

/* point3D overrided member function */

static double radius3D(struct point* self){

/* downcasting will cause warning */

     struct point3D* p=(struct poin3D*)self;

     return sqrt((double)(self->x*self->x+

                   self->y*self->y+

                   p->z*p->z));

}

static void display3D(struct point* self, const char* name){

     struct point3D* p=(struct poin3D*)self;

     printf("3D point %s: x=%d, y=%d, z=%d\n", name, self->x, self->y, p->z);

}

static void init_point3D(struct point3D* p, int z){

     p->z=z;

     ((struct point*)p)->radius=radius3D;  /* override */

     p->super.display=display3D;       /* override */

}

/* point 3D ctor */

struct point* create_point3D(int x, int y, int z){

     struct piont3D* p=(struct point3D*)malloc(sizeof(struct point3D));

     init_point((struct point*)p, x, y);

     init_point3D(p, z);

     return (struct point*)p;

}

平面上的彩色点直接使用父类的radius函数,但是需要override用于显示的display函数。

/* color point used its fathers radius methods,

 * only display function is overwrote

 */

static void display_color(struct point* self, const char* name){

     struct color_point* p=(struct color_point*)self;

     printf("color point %s: x=%d, y=%d, color=%d\n", name, self->x, self->y, p->color);

}

static void init_color_point(struct color_point* p, enum color_t color){

     p->color=color;

     p->super.display=display_color; /* only display function is overridden */

}

/* color point ctor */

struct point* create_color_point(int x, int y, enum color_t color){

     struct color_piont* p=(struct color_point*)malloc(sizeof(struct color_point));

     init_point((struct point*)p, x, y);

     init_color_point(p, color);

     return (struct point*)p;

}

 

现在可以测试一下这套继承体系的多态特性:

 

int main(int argc, char* argv[]){

     struct point* p=create_point(1, 1);

     printf("radius of p1=%f\n", p->radius(p));

     p->display(p, "p1");

     free(p);

     p=create_point3D(1, 1, 2);

     printf("radius of p2=%f\n", p->radius(p));

     p->display(p, "p2");

     free(p);

     p=create_color_point(1, 1, RED);

     printf("radius of p3=%f\n", p->radius(p));

     p->display(p, "p3");

     free(p);

     return 0;

}

 

调用方法和C++的如出一辙,运行结果如下:

radius of p1=1.414214

point p1: x=1, y=1

radius of p2=2.449490

3D point p2: x=1, y=1, z=2

radius of p3=1.414214

color point p3: x=1, y=1, color=0

 

输出结果也和预想的一致,至此“刀耕火种”的ANSI C基本实现了用C++Java体现出的面向对象三大特性。当然这里还有不完美的地方,比如没有办法限制访问权限。C++Java提供语言级别的public, private等关键字来实现一定程度的数据隐藏。在本文的实现方法中,虽然可以通过把方法置于C文件内部实现部分private意义上的隐藏,但是却暴露了所有的数据成员。另外C++Java都提供的函数overload,也没有给出有效的实现。但这已经不足以阻止用“刀耕火种”建立“繁荣时代”了。古代文明的魅力也正在于此。

 

盛世还有一些 代表性的城市繁荣,比如唐的长安,北宋的汴梁,南宋的杭州。虽然现代化的大都市有惊人的硬件环境,但是在缺乏这些现代化硬件的古代,依然能够实现城市的繁 荣。比如现代操作系统,天生支持多线程。可是在不支持多线程的系统上,依然不能阻止实现运算调度的尝试。这些尝试虽然不是本质上的并行处理(现代操作系统 在单CPU下也不是真正意义上的并行,而是时间片的调度,当然Intel最近热炒的双核不算在内),但是依然能够给使用者并发的感觉。

 

下面就用C++语言本身(不使用任何多线程库)实现多线程的感觉。其思路来自一种称之为ActiveObject[4]的模式。这种思路往往类似传统的戏剧。比如《三国演义》里面长板坡,开始情节单一,过一会就出现了两个条线(thread), 一条线赵云冲进去救刘备家小去了。另一条线张飞护着刘备跑,并且设置假伏兵。两条线同时发生,都要演出,可是却只有一个舞台。办法是舞台上先演一段赵云, 张飞等演员在后台休息,然后大幕落下,赵云等演员撤到后台休息,张飞他们出来再演一段。然后再次赵云上前台演,张飞去后台休息……如此往复。观众们丝毫不 觉得奇怪,而很自然的认为这两段故事同时发生。演出的关键在于合理的分隔每段故事上演的时间片,让观众没有割裂的感觉。这个思路其实就是单CPU,单进程实现多线程的思路。

 

首先定义一个线程类,这个就是张飞和赵云的故事这种并发故事的抽象描述:

class thread{

public:

     thread(int period):period_(period*1000),time_(0){};

     virtual void run()=0;

     virtual bool finish()=0;

     bool wait(){

          return (time_++%period_)!=0;

     }

private:

     long period_;

     long time_;

};

线程的构造函数中,导演可以事先决定每段故事的演员要在后台休息的时间period,每次休息时间一到(time_++%period0的时候),演员们就停止休息上前台演出(run)。故事的每条线有一个结束的标志finish,当故事结束时,就再也不用到舞台上演出了。

 

舞台调度的实现如下:

class schedular{

public:

     static schedular& instance(){

          static schedular inst;

          return inst;

     }

     void start(){

          while(!queue_.empty()){

              thread* p=queue_.front();

              queue_.pop_front();

              if(!p->wait())

                   p->run();

              if(!p->finish())

                   queue_.push_back(p);

              else

                   delete p;

          }

     }

     void add(thread* p){

          queue_.push_back(p);

     }

     ~schedular(){

          while(!queue_.empty()){

              delete queue_.front();

              queue_.pop_front();

          }

     }

private:

     schedular(){}

     schedular(const schedular&);

     const schedular& operator=(const schedular&);

     list<thread*> queue_;

};

舞台只有一个,所以被设计为singleton。在演出前,导演可以预先把这些故事线索(thread)加入到舞台调度的列表(queue_)中去,然后一旦演出开始(start),舞台调度就不断查看调度表,把表头的故事拿出来,看看是否他们应该上台,如果该上台了,就通知他们演出(run)一 段。这段演出结束后,调度询问他们所演的全部故事是否已经结束了,如果还没有,就把他们的故事再次放到调度表的最后面,然后去准备演下一条故事线索。一旦 这个故事线索演出完毕,比如说赵云已经抱着阿斗冲出来了,调度就不会再把故事放到调度表的最后。赵云就可以卸装喝水彻底休息了。

现在就可以给出一个具体的故事线索作为例子了:

class counter: public thread{

public:

     counter(const string name, int value, int s):thread(s), name_(name), value_(value){}

     void run(){

          cout<<"thread "<<name_<<": count "<<value_--<<"\n";

     }

     bool finish(){

          return (value_<=0);

     }

private:

     string name_;

     int value_;

};

这个故事就是:“从前有座山,山上有个庙,庙里有个和尚在将故事,讲的故事是:从前有座山……”这样的循环故事,每次上台演出就讲一句,直到讲到累了(value_0)为止。每次在后台休息s-1分后,上台讲一句。

 

此后就可以演出了,导演让两个这样的故事线索同时上演,间隔的时间不同:

int main(int argc, char* argv[]){

     schedular::instance().add(new counter(string("hello"), 20, 2));

     schedular::instance().add(new counter(string("me"), 10, 5));

     schedular::instance().start();

}

演出的效果如下:

thread hello: count 20

thread me: count 10

thread hello: count 19

thread hello: count 18

thread me: count 9

thread hello: count 17

thread hello: count 16

thread hello: count 15

thread me: count 8

thread hello: count 14

thread hello: count 13

thread me: count 7

thread hello: count 12

thread hello: count 11

thread hello: count 10

thread me: count 6

thread hello: count 9

thread hello: count 8

thread me: count 5

thread hello: count 7

thread hello: count 6

thread hello: count 5

thread me: count 4

thread hello: count 4

thread hello: count 3

thread me: count 3

thread hello: count 2

thread hello: count 1

thread me: count 2

thread me: count 1

 

作为现在的中 国人,多少对“古代盛世”怀有一些复杂的情节。有的人怀念,恨不得能够回到唐朝去生活;有的人理性地说光是晚上没有电就受不了。有时面对计算机,手机,汽 车这些东西,我也会怀疑究竟人类技术的发展是一种社会的进步还是倒退。有时候看到早上一路小跑赶公共汽车的人们生怕上班打卡迟到,我也会想究竟古代是否要 活的这么紧张。

 

现代人还是很难摆脱历史的阴影,we learn from history that people never learn from history,看着我们一遍一遍重复古人走过的轨迹——揭竿而起,励精图治,贪污腐败,天灾人祸,战乱流离……这样兴衰治乱的演出,上帝一定也觉得很没有意思吧。

 

参考书目:

[1] Apache httpd source code: http://httpd.apache.org/

[2] APR, apache runtime library source code: http://apr.apache.org/

[3] Peter. Wright. Beginning GTK/GNOME programming. Peer Information. 2000

[4] Robot C. Martin, Agile software development: principles, patterns, and practice. Printice Hall. 2002.


[English][上一篇][下一篇][目录][主页][联系我们: liuxinyu95@gmail.com]