加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 程序设计 > 正文

对象的完整性

发布时间:2020-05-24 10:56:58 所属栏目:程序设计 来源:互联网
导读:对象的完整性 对象是OOP的基本单元,由于维护一个对象需要很大的代价,所以设计一个对象也需要谨慎。 按照中国教科书的习惯,一般要把这个问题分解为对象的合理性、正确性和完整性。在这里我不想把人搞糊涂也不想把我搞糊涂,我只是提对象的完整性。当然也借

对象的完整性

对象是OOP的基本单元,由于维护一个对象需要很大的代价,所以设计一个对象也需要谨慎。

按照中国教科书的习惯,一般要把这个问题分解为对象的合理性、正确性和完整性。在这里我不想把人搞糊涂也不想把我搞糊涂,我只是提对象的完整性。当然也借鉴了牛人布鲁克斯的术语,他在《人月神话》里对系统概念的完整性推崇倍至。

对象的完整性,从正面的角度来说,就是指对象的函数接口是完备的;从反面的角度来说,就是不能残缺;从用户的角度来说,就是不能缺少某些函数接口而不能进行合理的操作。总而言之,一个完整的对象应该是合理的、正确的、完备的,能够让你完成你希望从这个对象得到的任何合理的操作。

以上都是废话,核心的问题是,如何让你设计的对象满足完整性。

我想从两个方面说这个问题,一个说从思想层面上,一个是从工具层面上。

1. 对象的完整性的意义

对象是对现实世界物质的抽象,应该反映物质的属性,反映物质的本质属性应该成为对象的成员函数(这里所说的成员函数都是非静态的成员函数,以下同)。例如对于一个长方形(rectangle)对象,有长、宽、面积、对角线的长度,这些都应该成为rectangle对象的成员函数。

物质本身的属性成为对象的成员函数,一般人们没有异议。但是对于物质之间的关系是否应该成为对象的成员函数,存在一些不同的看法。例如对于Rectangle类,两个Rectangle类对象之间的包含关系,Rectangle对象和点(Point)对象之间的包含关系,可以设计为成员函数:

class Rectangle

{

///////……………

bool contain(const Rectangle& other) const

{

// code here implement here

}

bool contain(const Point& pt) const

{

// code here implement here

}

/////……………..

};

当然也可以设计为全局函数:

bool contain(const Rectangle& a,const Rectangle& b)

{

// code here implement here

}

bool contain(const Rectangle& rect,const Point& pt)

{

// code here implement here

}

大部分的人认为设计为成员函数是更好的选择,因为作为成员函数使用似乎更加符合面向对象的精神,而使用全局函数则似乎返回到了遥远的面向过程设计的年代。但是我至少有两个理由认为全局函数是更好的选择:

1. 对于异质对象之间的关系来说,成员函数的归属没有必然的逻辑根据。例如对于RectanglePoint对象来说,contain关系的实现放在哪个类定义里面呢?你可以选择其中的一个,也可以选择全部,但是这样作都是设计者的主观选择,没有必然的逻辑根据。全局函数没有这个问题。

2. 如果物质之间的关系很多,会导致类对象定义的成员函数过多(有的一个Date类竟然定义了60多个的成员函数),成员函数过多会导致用户理解困难,设计者维护困难。这是因为成员函数一般会访问私有数据,而一旦私有数据的形式变动,那么大量的成员函数需要全部更改,维护起来十分的困难。全局函数一般通过成员函数访问类的私有数据,维护起来相对容易;而且全局函数会显著的减少成员函数的数量(一般不超过20个),用户的理解也比较容易。

所以你看,全局函数也有自己的优点。

因为两种方式都有各自的优劣,所以选择起来就有一定的犹豫。我得一般原则是:同质关系放在成员函数,异质关系设计为全局函数;尽量保持成员函数的数目不超过20。当然这是一般的原则,也有特殊的情况,这要设计者自己把握了。

当我们使用物质本身的属性和物质之间的关系设计类的时候,如果能够把物质的属性和关系抽象完备,那么类设计也就完备了。有的一些类并不对应与现实世界的物质,而是一些抽象的概念,例如容器类,这就需要更加谨慎的抽象类的属性和关系了。

从思想的层面上说,还可以从另外的一个角度说明问题。在artima网站2003年采访Bjarne Stroustrup的时候,有过这样的一句话:The functions that are taking any responsibility for maintaining the invariant should be in the class,意思是有责任维护类的不变性的函数应该成为类的接口。不变性归根结底也是物质的属性(本身属性或者关系属性),是此物质区别于彼物质的标示,是维持物质内部的合理状态。

维持类的合理状态就是类的不变性,这个解释可能更容易理解。比如一个Rectangle类对象,它的不变性就是长、宽大于0,面积是长宽的乘积等等,如果违反了这些不变性,就破坏了类内部的合理状态,类就不能称其为类了。所以Rectangle通过成员函数让你修改它的长宽,并且在成员函数中检查参数的范围,维护类的不变性。

从另外一个角度来说,如果一个类的成员变量的值可以为任意的,那么就没有必要把这个物质抽象为类,你可以把它抽象为struct。所以Bjarne Stroustrup说:I particularly dislike classes with a lot of get and set functions.。这样的类基本上就意味着它是一个struct

类本身的属性,类之间的关系;或者说类的不变性,是保证一个类成员函数完备的基础。思想深刻的牛人或许不需要验证就可以说他的类设计是完整的;但是对于吾辈之芸芸众生,则需要一定的手段来保证和验证类的完整性,着就需要我们从工具层面上说起。

2. 对象完整性的工具验证

我们保证对象完整性的工具就是:测试。

先从例子出发,还是Rectangle

class Rectangle

{

double width_,height_;

public:

Rectangle(double w,double h)

:width_(0),height_(0)

{

setWidth(w);

setHeight(h);

}

double getWidth() const

{

return width_;

}

void setWidth(double w)

{

if(w > 0){

width_ = w;

}

}

double getHeight() const

{

return height_;

}

void setHeight(double h)

{

if(h > 0){

height_ = h;

}

}

};

测试的时候,很容易需要测试Rectangle的面积:

void test()

{

Rectangle rect(3,4);

assertEqual(rect.getWidth() == 3);

assertEqual(rect.getHeight() == 4);

/////...

assertEqual(rect.getArea() == 12);

}

很显然需要为Rectangle补充一个求面积的函数:

class Rectangle

{

/////...

double getArea() const

{

return width_ * height_;

}

};

随着测试的继续进行,Rectangle之间的关系测试也会出现,从而也需要把相关的函数添加进去,对象的完整性就会逐渐的得到满足。当你感觉没有更多测试的时候,对象的完整性基本就得到保证了。

“这怎么看起来象TDD?”不错,是和TDD很像。不过TDD的设计者有他们的出发点:编写整洁可用的代码(clean code that works),而我这里的出发点对象的完整性,殊途同归吧:)

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读