软件设计本质论(Essential Design) —白话面向对象
转载时请注明出处:http://blog.csdn.net/absurd/
不同的人在谈面向对象编程(OOP)时所指的含义并不相同。有人认为任何采用图形界面的应用程序都是面向对象的。有人把它作为术语来描述一种特别的进程间通信机制。还有人使用这个词汇是另有深义的,他们其实是想说:“来啊,买我的产品吧!”我一般不提OOP,但只要提到,我的意思是指使用继承和动态绑定的编程方式。 --《C++沉思录》
《C++沉思录》说的是十几年前的事了,现在大家对面向对象的回答已经是众口一词:封装、继承和多态。大家都知道,在面向对象中,一辆汽车是一个对象,汽车这个概念是一个类。汽车有漂亮的外观,把各种内部原理都隐藏起来了,司机不必知道它的内部工作原理仍然能开车,即使汽车随技术的进步不断升级,对司机也没有什么影响,这就是封装的好处。
汽车是交通工具的一种,汽车是一个类,交通工具也是一个类,而交通工具类包括了汽车类,从而具有更广泛的意义。这种从抽象到具体的关系就是继承关系,我们可以说汽车类继承了交通工具类,汽车类是交通工具类的子类,交通工具类是汽车类的父类。
作为交通工具,它肯定可以运动(move),从甲地运动到乙地,就起到了交通的作用。轮船是一种交通工具,所以轮船类也是交通工具类的子类。同样是运动,轮船的运动和汽车的运动方式肯定有所不同,这样以不同的方式完成同样的功能就叫多态。
关于对象:对象就是某一具体的事物,比如一个苹果, 一台电脑都是一个对象。每个对象都是唯一的,两个苹果,无论它们的外观有多么相像,内部成分有多么相似,两个苹果毕竟是两个苹果,它们是两个不同的对象。对象可以是一个实物,也可能是一个概念,比如某个苹果对象是实物,而一项政策可能就是一个概念性的对象了。
关于类:对象可能是一个无穷的集合,用枚举的方式来表示对象集合不太现实。抽象出对象的特征和功能,按此标准将对象分类,这就引入类的概念。类就是一类事物的统称,类实际上就是一个分类的标准,符合这个分类标准的对象都属于这个类。当然,为了方便起见,通常只需要抽取那些,对当前应用来说是有用的特征和功能。
关于抽象类:类是对对象的抽象,比如,苹果是对所有具体的苹果的抽象。如果我们对苹果这个类进行一步抽象,可以得到一个水果类。这种对类本身进行抽象而得到的类,就是抽象类。抽象类不像普通类,它是没有对象与之对应的。像苹果类,你总是可以拿到一个叫苹果的东西,而对于水果类,根本没一个真正叫水果的东西。你可以说一个苹果是一个水果,从逻辑上讲没有错,但没有什么意义。一般在程序中,抽象类是不能实例化的。
关于面向对象:面向对象就是以对象为中心。为什么不说是面对类,而说是面向对象呢?类是对象的集合,考虑类实际上也是在考虑对象,有时甚至并不严格的区分它们。所以说面向对象一词比面向类更确切。
既然以对象为中心,面向对象所考虑的内容自然是对象、对象间的协作、对象的分类、类之间的关系等等,由此引申了出几个重要的概念。
1. 封装
what:对象也有隐私,对象的隐私就是对象内部的实现细节。要想对象保持良好的形象就要保护好对象隐私,所谓的封装其实就是保护对象隐私。当然,没有人能完全隐藏自己的隐私,比如你去转户口时,你不得不透露自己的家庭信息和健康状况。另外,在不同的场合所透露隐私的数量也不一样,朋友和家人可能会知道你更多隐私,同事次之,其他人则知道得更少。面向对象也考虑了这些实际的情况,所以像C++之类的语言有public/private/protected/friend等关键字,以适应于不同的情况。
why:封装可以隔离变化。据以往的经验,我们知道内部实现是容易变化的,比如电脑在不断的升级,机箱还是方的,但里面装的CPU和内存已是今非昔比了。变化是不可避免的,但变化所影响的范围是可以控制的,不管CPU怎么变,它不应该影响用户使用的方式。封装是隔离变化的好办法,用机箱把CPU和内存等等封装起来,对外只提供一些标准的接口,如USB插口、网线插口和显示器插口等等,只要这些接口不变,内部怎么变,也不会影响用户的使用方式。
封装可以提高易用性。封装后只暴露最少的信息给用户,对外接口清晰,使用更方便,更具用户友好性。试想,如果普通用户都要知道机箱内部各种芯片和跳线,那是多么恐怖的事情,到现在为止我甚至还搞不清楚硬盘的跳线设置,幸好我没有必要知道。
how:在C语言中,可以用结构+函数来模拟类的实现,而用这种结构定义的变量就是对象。封装有两层含义,其一是隐藏内部行为,即隐藏内部函数,调用者只能看到对外提供的公共函数。其二是隐藏内部信息,即隐藏内部数据成员。现在都建议不要对外公开任何数据成员,即使外部需要知道的数据成员,也只能通过函数获取。
在C语言中要隐藏内部函数很简单:不要它把放在头文件中,在C文件中定义时,前面加static关键字,每个类放在独立的文件中。这样可以把函数的作用范围限于当前文件内,当前文件只有类本身的实现,即只有当前的类自己才能看到这些函数,这就达到了隐藏的目的。
在C语言中要隐藏数据成员较为麻烦,它没有提供像C++中所拥有的public/protected/friend/private类似的关键字。只能通过一些特殊方法模拟部分效果,我常用的方法有两种。
其一是利用C的特殊语法,在头文件中提前声明结构,在C文件才真正定义它。这样可以把结构的全部数据信息都隐藏起来。因为外部不知道对象所占内存的大小,所以不能静态的创建该类的对象,只能调用类提供的创建函数才能创建。这种方法的缺陷是不支持继承,因为子类中得不到任何关于父类的信息。如:
头文件:
struct _LrcPool; typedef struct _LrcPool LrcPool;
LrcPool* lrc_pool_new(size_t unit_size, size_t n_prealloc_units); void* lrc_pool_alloc(LrcPool* thiz); void lrc_pool_free(LrcPool* thiz, void* p); void lrc_pool_destroy(LrcPool* thiz);
|
C文件:
struct _LrcPool { size_t unit_size; size_t n_prealloc_units; };
LrcPool* lrc_pool_new(size_t unit_size, size_t n_prealloc_units) { LrcPool* thiz = LRC_ALLOC(LrcPool, 1);
if(thiz != NULL) { thiz->unit_size = unit_size; thiz->n_prealloc_units = n_prealloc_units; }
return thiz; } |
其二是把私有数据信息放在一个不透明的priv变量中。只有类的实现代码才知道priv的真正定义。如:
头文件:
struct _LrcBuilder { LrcBuilderBegin on_begin; LrcBuilderOnIDTag on_id_tag; LrcBuilderOnTimeTag on_time_tag; LrcBuilderOnLrc on_lrc; LrcBuilderEnd on_end; LrcBuilderDestroy destroy;
char priv[1]; };
|
C文件:
struct _LrcDumpBuilder { FILE* fp; };
typedef struct _LrcDumpBuilder LrcDumpBuilder; LrcBuilder* lrc_dump_builder_new(FILE* fp) { LrcDumpBuilder* data = NULL; LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) + sizeof(LrcDumpBuilder), 1);
if(thiz != NULL) { thiz->on_begin = lrc_dump_builder_on_begin; thiz->on_id_tag = lrc_dump_builder_on_id_tag; thiz->on_time_tag = lrc_dump_builder_on_time_tag; thiz->on_lrc = lrc_dump_builder_on_lrc; thiz->on_end = lrc_dump_builder_on_end; thiz->destroy = lrc_dump_builder_destroy; data = (LrcDumpBuilder*)thiz->priv; data->fp = fp != NULL ? fp : stdout;
|