现在的位置: 首页 > 综合 > 正文

面向对象理论(9)-More Topics

2012年01月25日 ⁄ 综合 ⁄ 共 2797字 ⁄ 字号 评论关闭

让我们来谈一些关于面向对象理论的有趣的话题。

正方形和矩形的故事

正方形是一个特殊
的矩形。



                                ----《几







何》

在面向对象理论的讨论中,有一个很令人不解的问题,就是为什么正方形不能从矩形派生?因为从几何数学的定义出发,正方形就是一个矩形,一个特殊的矩形。
首先,我们要看看矩形的封装。我们这里有很多种矩形的封装。

class
 Rect
{
    Point p1; 

//
top-left point


    Point p2; 
//
bottom-right point


};


//
OR


class
 Rect
{
    

int
 x1;
    

int
 y1;
    

int
 x2;
    

int
 y2;
};


//
OR


class
 Rect
{
    Point p; 

//
top-left point


    
int
 width;
    

int
 height;
};


//
More...


我们可以给出很多种矩形的封装形式,它们是不是一个对象呢?是,只要我们写出同样的操作接口,表达同样的行为就可以了,那么对象的使用者是不关心,也不可
能关心对象的内部结构的(除非是C/C++用sizeof这样的操作符去打量这个类型,然后它也不过是知道个大小)。那么就是说,决定了一个对象的根本在
于对象的行为。

以上这段话,也许是我一厢情愿的想法,但是我也有我的代码作为证据。
C/C++代码可以如此构造:
我们把上述3种Rect分别定义在rect1.h, rect2.h, rect3.h,然后我们在对应的.cpp文件中实现它们。它们的成员变量尽管不同,但是接口都是一致的。
然后,我们在某个函数中使用这些类型,例如:
void setRectWidth(Rect* pRect, int width)
{
    pRect->setWidth(width);
}
OK, 当我们想使用第一种声明的class Rect的时候,我们只要include "rect1.h"即可。
至少,这部分代码是可以编译通过的。而且,setWidth方法会依照不通的实现,以不同的角度(方式)改变成员。


那么,既然如此,我们就再看看正方形的行为。

class
 Square
{
    

void
 setWidth(...) {}
}


这里的setWidth和Rect类中的setWidth(),setHeight()在行为上是不同的。因为Square的setWidth会改变整个
正方形的横向和纵向两个方向的边长,而Rect的setWidth则(应该)不是。所以会产生这种尴尬的原因在于,在数学上,我们承认正方形是矩形的一个
特例,但是在面向对象上,正方形在行为上根本区别于矩形。所以,要明确的是,我们以往说A Cat is-a
Animal。是在承认猫在行为上具有动物的(共同、普遍)行为等。
更多的内容,需要大家参考Liskov的LSP原则【1】,使用该原则,我们可以反证在面向对象上,正方形不是一个矩形。

对象的Clone语义

我们可以Clone一个对象嘛?当然了,但是不是什么时候都可以Clone一个对象的。因为那关系到一个逻辑的问题。

很多人一直都在争论Michael Bay的电影《逃出克隆岛》的中关于记忆可以被克隆的大胆设想。

这是一个逻辑的问题:它不是语法的问题,所以编译器帮助不了你。我们想这样的一个问题,如果我们有一个对象是Rect,我们想复制一个Rect,它们的大
小相同,OK,我们可以Clone它。而现在,我们有一个Socket对象,你想复制它嘛?一个构造完毕的Socket对象应该和Local的一个端口
binding在一起,而端口就是资源。那么当我们有了两个对象,其中一个是另外一个的复制品,那么端口这个资源怎么办呢?也要复制嘛(怎么复制?)?
在Java中,有Cloneable接口和CloneNotSupportedException异常,一个对象的可克隆性,就被这两个设施淡淡地描绘出来了,而实际上,许多人并没有意识到这个事情的存在。

这并不难于理解,像“文件”,“管道”,“流”对象,都应该是不可复制的对象。
需要说明的有两点:【2】
1. 对象是否可以被Clone,这是一个设计问题,毕竟作为对象的描述-内存数据,当然是可以被Copy的。
2. 我们也许会给Socket classes的对象找到一个需要复制出它们的副本的设计理由,我不是很确定。

Socket classes

我们已经讲了这么多的内容了,希望你可以从中体会到那些我想传达的信息,最后,我们再举一个关于Socket classes的例子,来结束我们关于面向对象理论的阐述。
而具体这个例子,是关于对象模型?粒度划分?还是关于类型安全,或者是行为确定,或者是全部都包括呢?就需要你来体会了。【3】

在Win32,或者是UNIX编程中,我们通常能看到这样的(宏)代码:

#define
 SOCKET
 unsigned int


也就是说,我们用到了Socket
API,socket,select,listen等函数,所需要的参数的数据类型,也不过是一个整数。既然是一个整数,那么在C/C++中,就难以保证
其类型安全了,一个整数值,是一个处于侦听的Socket,还是一个TCP
Socket,一个UDP的Socket,甚至,它是不是Socket,都是一个未知数。那么在这个环节上,类型安全的问题就显得尤为突出了。

以MFC为例,CAsyncSocket,CSocket封装了SOCKET,至少目前,你再使用Socket的时候,你知道这个对象是一个Socket了,而不是一个其他的什么对象。
但是,这远远不够的,看CAsyncSocket的实现代码,我们就知道了,它把侦听的Socket,和用于通信的Socket,揉二者之行为于一个类中。那么,我们获得的一个CAsyncSocket对象的引用,我们如何能确定它就是一个Server Socket?
当然了,设置一个标志成员,就可以解决这个问题了。然后,这并非是一个好的办法。
到了Java中,我们就可以看到,这两种事物,有了不同的类型,Socket和ServerSocket,再也不会犯下错误了。在ACE中,在Ruby的
库中,都可以看到如此这般的设计。从根本上讲,这二者,尽管同属于TCP
Socket的实现类,但是其行为有着根本的不同,一个Listen,一个Send和Receive。

于是在SOCKET->MFC CSocket->Java ServerSocket Socket的演化过程中,我们看到了对象模型的建立,粒度的划分,类型安全,以及行为的确定,还有许多许多,于是需要我们再去挖掘。



【1】:在后面的章节中,我们会更多地讨论LSP的。
【2】:就此点,我也需要更多的思考,也希望得到更多的想法。
【3】:这是我在CSDN BLOG原稿的最后一篇,所以有“最后”等字样。

抱歉!评论已关闭.