对象的完整性

前端之家收集整理的这篇文章主要介绍了对象的完整性前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

对象的完整性

对象是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 @H_118_403@@H_524_404@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

@H_888_502@class Rectangle

@H_888_502@{

@H_888_502@ double width_,height_;

@H_888_502@public:

@H_888_502@ Rectangle(double w,double h)

@H_888_502@ :width_(0),height_(0)

@H_888_502@ {

@H_888_502@ setWidth(w);

@H_888_502@ setHeight(h);

@H_888_502@ }

@H_888_502@ double getWidth() const

@H_888_502@ {

@H_888_502@ return width_;

@H_888_502@ }

@H_888_502@ void setWidth(double w)

@H_888_502@ {

@H_888_502@ if(w > 0){

@H_888_502@ width_ = w;

@H_888_502@ }

@H_888_502@ }

@H_888_502@ double getHeight() const

@H_888_502@ {

@H_888_502@ return height_;

@H_888_502@ }

@H_888_502@ void setHeight(double h)

@H_888_502@ {

@H_888_502@ if(h > 0){

@H_888_502@ height_ = h;

@H_888_502@ }

@H_888_502@ }

@H_888_502@};

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

@H_888_502@void test()

@H_888_502@{

@H_888_502@ Rectangle rect(3,4);

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

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

@H_888_502@ /////...

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

@H_888_502@}

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

@H_888_502@class Rectangle

@H_888_502@{

@H_888_502@ /////...

@H_888_502@ double getArea() const

@H_888_502@ {

@H_888_502@ return width_ * height_;

@H_888_502@ }

@H_888_502@};

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

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

原文链接:https://www.f2er.com/javaschema/288355.html

猜你在找的设计模式相关文章