开一个讨论,说说我封装的看法【初学者谨慎观看,未成年人请在家长监督下观看:)】

上一篇Post效果不是很好,既然大家喜欢讨论,我就还是用讨论的形式来进行。
这次还是接着上一次的话题,封装,可能是因为没有表述清楚,所以导致了很多Tx有所疑问,所以这里更加详细的阐述我对于封装的看法。
===============================申明================================
本文讨论环境在纯面向对象语言环境下(静态类型语言,比如C#,JAVA),C++使用
者请注意某些和C++的不同之处。如果不加注释,本文的讨论不涉及ORM以及持久化等技术
==================================================================

首先什么是封装就不用多说了,上次说到,类是用来描述是什么,属性表示有什么,而行为表示能够执行什么动作的时候没有进一步对如何封装这些概念来做进一步的阐述。

对属性的封装:
属性表示对象的状态,而状态在程序里是使用数据来表示的,所以类中使用变量的来存储。所以一个类的内部成员变量是用来存储对象的状态的。
为了封装状态,我们需要将变量的访问性设置为private。
但是很多时候对象的某些状态是需要外界可见的(外部可见的状态,很容易理解,如果对象的状态都不清楚,那不是两眼一抹黑闷头操作),打个比方,汽车对象,起码来说,当前跑的速度,车身颜色,车牌号码都应该是外部能够了解的状态。那么,在java中,只有用方法来暴露了。到了后来就成了GetXXX,SetXXX方法,也就是POJO对属性的表示了。而在C#中专门提供了属性结构,其实在编译后还是两个方法。

属性不管在Java还是在C#中都是对状态的封装的一种形式。既满足了封装,也满足了对对象内部需要暴露状态访问的要求。
属性虽然说本质上就是两个方法(可以只有一个),但是属性的行为是有限制的(个人认为,当然你非要什么都写进去我也没法)。
其一,属性内的过程只操作属性所对应的内部状态的变量
其二,属性如果不需要接受外部的数据最好不要写set
属性内的过程是用来约束读取和写入状态数据的判断逻辑,所以应该是判断的代码而不是行为的代码。不要当成了普通的方法来写。

对行为的封装:
对象的行为,在面向对象的语言里使用方法来封装行为。我们Call一个方法,也就是通知了一个对象执行某种操作。这种操作由于是对象的方法在执行,所以对内部的状态可以全权操作。但是如果一个对象的行为设计到涉及到另个对象的状态,比如我们需要打开数据库首先述要判断Connection对象是处于什么的状态。所以如果是在行为里需要依赖另外对象的状态,那么就需要另外一个对象用属性暴露出这个状态出来。

但是,有一种方式我认为是不恰当的。比如,要保存对象的状态或者要设置对象的状态,都绕过了行为,而直接把属性当作公共出入口。从属性里读出来存入数据库,或者从数据库里读出来再挨个赋值给对象。最后再省略,这个类就连方法都没了。就我的看法,没有行为能力的人是死人(起码也是个植物人,高位截瘫什么的),没有行为能力的类,是死类:)。【个人觉得对一个对象的公有属性赋值,然后调用依赖于这些状态数据的方法是可以说得过去的,就和装好子弹扣扳机的过程一样,给这些属性赋值就是装子弹,执行方法就是扣扳机】

最后一个对象是不会有耦合的。对象多了,对象直接必要要打交道。在满足封装性的条件下,对象之间打交道主要通过两个接口,一个是属性,一个就是方法(属性方法都是外部对类状态改变的接口),当然在C#里还可以通过事件,不过事件在本质上还是对方法的调用。

最后我们必须来说说耦合的问题

耦合度是用来度量代码块之间联系的程度的标准
耦合度是从模块外部考察模块的独立性程度。它用来衡量多个模块间的相互联系。一般来说,耦合度应从以下三方面来考虑,即:
耦合内容的数量,即模块间发生联系的数据和代码的多少,同这些数据和代码发生联系的模块的多少,多的耦合强,少的耦合弱;
模块间的耦合类型。耦合类型有以下几种方式:
①独立耦合
②数据耦合
③控制耦合
④公共耦合
⑤内容耦合
下面重点对各种类型的耦合作进一步的说明。
(1)独立耦合
两个模块完全没联系
(2)数据耦合
指两个模块彼此交换数据。如一个模块的输出数据是另一个模块的输入数据,或一个模块带参数调用另一个模块,下层模块又返回参数。应该说,在一个软件系统中,此种耦合是不可避免的,且有其积极意义。因为任何功能的实现都离不开数据的产生、表示和传递。数据耦合的联系程度也较低。譬如调用方法
(3)控制耦合
若在调用过程中,两个模块间传递的不是数据参数而是控制参数,则模块间的关系即为控制耦合。控制耦合属于中等程度的耦合,较之数据耦合模块间的联系更为紧密。但控制耦合不是一种必须存在的耦合。当被调用模块接收到控制信息作为输入参数时,说明该模块内部存在多个并列的逻辑路径,即有多个功能。
举个例子
public void Action(bool param)
{
    if(param)
    {
        //do something
    }else
    {
        //do something
    }
}
这个方法就存在控制耦合。
控制变量用以从多个功能中选择所要执行的部分,因而控制耦合是完全可以避免的。排除控制耦合可按如下步骤进行:
①找出模块调用时所用的一个或多个控制变量;
②在被调模块中根据控制变量找出所有的流程;
③将每一个流程分解为一个独立的模块;
④将原被调模块中的流程选择部分移到上层模块,变为调用判断。
通过以上变换,可以将控制耦合变为数据耦合。由于控制耦合增加了设计和理解的复杂程度,因此在模块设计时要尽量避免使用。当然,如果模块内每一个控制流程规模相对较小,彼此共性较多,使用控制耦合还是合算的。
(4)公共耦合
公共耦合又称公共环境耦合或数据区耦合。若多个模块对同一个数据区进行存取操作,它们之间的关系称为公共耦合。公共数据区可以是全程变量、共享的数据区、内存的公共复盖区、外存上的文件、物理设备等。当两个模块共享的数据很多,通过参数传递可能不方便时,可以使用公共耦合。公共耦合共享数据区的模块越多,数据区的规模越大,则耦合程度越强。公共耦合最弱的一种形式是:两个模块共享一个数据变量,一个模块只向里写数据,另一个模块只从里读数据。
当公共耦合程度很强时,会造成关系错综复杂,难以控制,错误传递机会增加,系统可靠性降低,可理解、维护性差。
(5)内容耦合
内容耦合是耦合程序最高的一种形式。若一个模块直接访问另一模块的内部代码或数据,即出现内容耦合。内容耦合的存在严重破坏了模块的独立性和系统的结构化,代码互相纠缠,运行错综复杂,程序的静态结构和动态结构很不一致,其恶劣结果往往不可预测。
内容耦合往往表现为以下几种形式:
①一个模块访问另一模块的内部代码或数据;
②一个模块不通过正常入口而转到另一个模块的内部(如使用GOTO语句或JMP指令直接进入另一模块内部,C#和JAVA里基本上不会出现,C++情况不明,望C++达人提供有效情报);
③两个模块有一部分代码重迭(可能出现在汇编程序中,在一些非结构化的高级语言,如COBOL中也可能出现);
④一个模块有多个入口(这意味着一个模块有多种功能)。
这里除了2,3两种情况,1和4都可能在面向对象的语言的代码里出现。
第一种情况主要是本来该类内部行为共享的公共方法被错误的设置为public并且被外部的对象使用了。或者是直接将一个对象的属性读取出来,运算的结果又重新写回了对象的另一个属性。还可能有其它情况,不过限于个人能力,暂不知晓,有知道的请告知。
就我个人来说,假如A类的对象有个Count属性,那么在累加的时候使用a.Count++我觉得还OK啦,还没有洁癖到非要去弄一个Upgrade的方法上去,当然如果这个Upgrade的动作还涉及到其他的状态,那就还是多个方法出来好了。
一般讲,在模块划分时,应当尽量使用数据耦合。少用控制耦合(尽量转成数据耦合),限制公共耦合的范围,完全不用内容耦合。

所以说封装并不是表明就不耦合了,但是封装可以降低耦合度。对于耦合度来说我们是应该尽量降低的(在时间金钱允许的情况下),低耦合代表着在应对变化时我们能够更加从容应对。
 

posted on 2007-09-23 01:41  亚历山大同志  阅读(5935)  评论(12编辑  收藏  举报

导航