面向对象中的类间关系

http://blog.csdn.net/chevydream/archive/2008/12/24/3594361.aspx

说明:下面的讨论是在纯粹的面向对象语言基础上展开的,并且在论述的过程中不再详细区分“类间关系”和“对象间关系”两个概念。
1 类间关系概述——依赖与耦合

唯物辩证法对事物间的联系做了如下论述:联系是指一切事物之间和事物内部各要素之间的相互影响、相互制约和相互作用。联系是事物本身所固有的客观现象,是不依人的主观意志为转移的,不是人们强加给事物的。世界上没有孤立存在的事物,每一种事物都是和其他事物相联系而存在的,这是一切事物的客观本性。

同样的道理,软件系统中不会有单独存在的类,系统中的每个类,都因要跟其它类发生某种联系,才得以存在,没有了与其它类的联系,这个类也就失去了它存在的意义。我们常用依赖和耦合来描述类间的关系,简要论述如下:

在某个软件系统中,如果一个类发生改变会引起另一个类发生变化则称这两个类之间存在(广义的)依赖关系。类间的依赖关系不仅可以是结构性的(由于类之间的继承和关联引起),也可以是行为性的(由于类的参数变化以及类之间消息传递机制等引起)。有此可见,依赖就是关系,代表了类之间的联系。

耦合是对两个类之间存在的依赖关系的一个量度,不同的类发生依赖了关系,也就意味着发生了耦合。不同的耦合,代表了依赖程度的差别,我们通常以“粒度”为概念来分析其耦合的程度。

依赖和耦合在我看来是对一个问题的两种表达,依赖阐释了耦合本质,而耦合量化了依赖程度。因此,我们对于关系的描述方式,就可以从两个方面的观点来分析:

从依赖的角度而言,可以分类为:

无依赖,代表没有发生任何联系,所以二者相互独立,互不影响,没有耦合关系。

单向依赖,关系双方的依赖是单向的,代表了影响的方向也是单向的,其中一个类发生改变,会对另外的类产生影响,反之则不然,耦合度不高。

双向依赖,关系双方的依赖是相互的,影响也是相互的,耦合度较高。

从耦合的角度而言,可以分类为:

零耦合,表示两个类没有依赖。

具体耦合,如果一个类持有另一个具体类的引用,那么这两个类就发生了具体耦合关系。所以,具体耦合发生在具体类之间的依赖,因此具体类的变更将引起对其关联类的影响。

抽象耦合,发生在具体类和抽象类的依赖,其最大的作用就是通过对抽象的依赖,应用面向对象的多态机制,实现了灵活的扩展性和稳定性。

类间关系从依赖的强弱程度和持久性上区分,可以大体的分为如下两类:

类间行为关系(弱,暂时):(狭义的)依赖

类间结构关系(强,持久):泛化,关联


2 类间行为关系——依赖(Dependency):

意义:" ... uses a ..."

UML表示法:虚线 + 箭头;箭头指向被依赖元素。

(狭义的)依赖关系是类间最弱的一种关系,一个类(狭义的)依赖于另一个类是指这两个类之间存在一种短暂的,偶然的,行为性的关系,如同陌生人在街头接肩而过、互致问候。我们之所以把类间的这种关系非正式的称作“行为关系”,是团为它来自于与对象y相关的对象x做出的行为或动作。

在这个短暂的关系中,依赖的对象通过调用被依赖对象的方法来获取它提供的服务,或配置被依赖的对象;被依赖的对象只是作为一种工具在使用,依赖对象并不长期持有对它的引用。这里的重点在于“临时”:对象x完成与y的通讯后,就会把引用扔回给y。

(狭义的)依赖一般的表现方法是:对象x要么以方法参数的形式临时持有指向对象y的引用;要么从另一对象z处请求对象y的句柄;要么定义对象y为自己某个方法的局部变量;要么对类Y的静态方法进行直接引用。

(狭义的)依赖是一种使用关系,如果A依赖于B,则A可能使用了B的接口,此时当B的接口发生变化后就会影响A。

(狭义的)依赖总是单向的:类A的变化会影响类B,但反之不成立,那么B和A的关系是依赖关系,B依赖A。

例子1:自行车和打气筒。自行车打气需要找个打气筒来实现(如果你用那种袖珍气筒随时别在自行车上的话,就是关联了)。

Bicycle类和Pump类之间是依赖关系,在Bicycle类中无需定义Pump类型的变量。Bicycle类的定义如下:

在现时生活中,通常不会为某一辆自行车配备专门的打气筒,而是在需要充气的时候,从附近某个修车棚里借个打气筒打气。在程序代码中,表现为Bicycle类的expand()方法有个Pump类型的参数。以下程序代码表示某辆自行车先后到两个修车棚里充气:

myBicycle.expand(pumpFromRepairShed1); //到第一个修车棚充气

myBicycle.expand(pumpFromRepairShed2); //若干天后,到第二个修车棚充气

例子2:一个人自创生就需要不停的呼吸,而人的呼吸功能之所以能维持生命就在于吸进来的气体发挥了作用,所以说空气只不过是人类的一个工具,而人并不持有对它的引用。


3 类间结构关系——继承与关联

如果把类间的行为关系——(狭义的)依赖,比作同陌生人在街头接肩而过、互致问候。显然,更多时候你需要和一些人保持更为重要和持久的关系(家人、朋友、同事等等),同样,类之间也有建立更持久关系,或者说是必然性关系的需要。我们将这种持久关系非正式的称作“结构关系”。

类间的结构关系有很多种,在大的类别上可以分为以下两种:

纵向关系:泛化(包括类继承、接口继承、实现)

横向关系:关联(包括一般关联、聚合、组合)

 
4 纵向结构关系——泛化(Generalization)

意义:"... is a ..."

UML表示法:实线 + 空心箭头;箭头指向父类元素。

泛化关系是一般元素和具体元素之间的一种分类关系。具体元素与一般元素在共有特征方面完全一致,但具体元素除共有特征外还包含一些额外的信息。在允许使用一般元素的场合,可以使用具体元素的实例。

在实际生活中,有许多东西都具有共同的特征。例如,狗和猫都是动物。对象也可以具有共同的特征,您可以使用它们所属类之间的泛化关系来阐明这些共同特征。通过将共同特征抽取到它们所属的类中,可以在将来更容易地变更和维护系统。

类间的泛化关系表示一个类对另一个类的继承。继承而得的类称为后代。被继承的类称为祖先。继承意味着祖先的定义(包括任何特征,如属性、关系或对其对象执行的操作)对于后代的对象也是有效的。泛化关系是从后代类到其祖先类的关系。

泛化关系包括类与类之间的继承关系,接口与接口之间的继承关系,或类对接口的实现关系。这三种关系基本类似,不再具体说明。

在泛化关系中,父模型元素可以具有一个或多个子模型元素,且任何子模型元素可以具有一个或多个父模型元素。更常见的情况是一个父模型元素具有多个子模型元素。

例子1:下图说明了一个销售多种商品的 Web 站点的电子商务应用程序。该应用程序具有一个 InventoryItem 类,该类是父类(也称为超类)。此类包含所有商品使用的属性(例如,Price)和操作(例如,setPrice)。

在定义该父类之后,为每种类型的商品(例如,书籍和 DVD)都创建了子类。Book 类使用 Inventory 类中的属性和操作,然后对其添加属性(例如,author)和操作(例如,setAuthor)。DVD 类也使用 Inventory 类中的属性和操作,但是将对它添加属性(例如,manufacturer)和操作(例如,setManufacturer),它们与添加至 Book 类的属性和操作不同。

 
5 横向结构关系:关联(Association)

类间的横向关系被广义的称为关联,按照UML的建议大体上可以分为三种:关联(Association)、聚合(Aggregation)、组合(Composition)。其中,聚合是一种特殊的关联,组合又是一种特殊的聚集。

因为对象需要通过attribute的形式来持久地维护相关对象的句柄,才能保持这样的关系,关联关系就变成了对象数据结构的一部分。因此我们可以说,关联是类之间的词法连接,使一个类知道另一个类的公开属性和操作。

类间的横向关系较为微妙,它们的强弱关系是没有异议的:关联 < 聚合 < 组合;然而它们三个之间的差别却又不那么好拿捏,需要好好体会。

 
5.1 关联(Association):

意义:" ... has a ..."

UML表示法:实线 + 箭头;箭头指向被关联对象

所谓关联就是某个对象会长期的持有另一个对象的引用,并在需要的时候调用这个对象的方法。关联的两个对象彼此间没有任何强制性的约束,被关联的类间通常可以被独立的考虑,只要二者同意,可以随时解除关系或是进行关联,它们在生命期问题上没有任何约定。被关联的对象还可以再被别的对象关联,所以关联是可以共享的。

关联一般的表现方法是对象的实例变量。例如:class B { B b = new B(); void methodA() { b.methodB(); } }

关联有单向和双向之分。若类A单向关联指向类B,则在类A中存在一个属性B b。单向关联如:person知道house的公开属性和操作,而house不知道person的;person可以向house发消息,而house不可以向person发消息。如果类A与类B双向关联,则在类A中存在一个属性B b,在类B中存在一个属性A a。双向关联如:you和your friend都知道对方的公开属性和操作;you可以给your friend发消息,your friend也可以给你发消息。

例子1:人从生至死都在不断的交朋友,然而没有理由认为朋友的生死与我的生死有必然的联系,故他们的生命期没有关联,我的朋友又可以是别人的朋友,所以朋友可以共享。

例子2:自行车和主人,每辆自行车属于特定的主人,每个主人有特定的自行车。Person类与Bicycle类之间存在关联关系,这意味着在Person类中需要定义一个Bicycle类型的成员变量。以下是Person类的定义:

在现时生活中,当你骑自行车去上班时,只要从家里推出自己的自行车就能上路了,不象给自行车打气那样,在需要打气时,还要四处去找修车棚。因此,在Person类的goToWork()方法中,调用自身的bicycle对象的run()方法。假如goToWork()方法采用以下的定义方式:

/** 骑自行车去上班 */

public void goToWork(Bicycle bicycle){

    bicycle.run();

}

那就好比去上班前,还要先四处去借一辆自行车,然后才能去上班。

 
5.2 聚合(Aggregation):

意义:" ... owns a ..."

UML表示法:空心菱形 + 实线 + 箭头;箭头指向被拥有对象

聚合是强版本的关联,强调的是整体与部分之间的关系。它暗含着一种所属关系以及生命期关系。被聚合的对象还可以再被别的对象关联甚至聚合,所以被聚合对象是可以共享的。

与关联关系一样,聚合关系也是通过实例变量来实现的。关联关系和聚合关系在语法上是没办法区分的,从语义上才能更好的区分两者的区别。

(1)关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。

(2)对于具有关联关系的两个对象,多数情况下,两者有独立的生命周期。比如自行车和他的主人,当自行车不存在了,它的主人依然存在;反之亦然。但在个别情况下,一方会制约另一方的生命周期。比如客户和订单,当客户不存在,它的订单也就失去存在的意义。而对于具有聚集关系(尤其是强聚集关系)的两个对象,整体对象会制约它的组成对象的生命周期。部分类的对象不能单独存在,它的生命周期依赖于整体类的对象的生命周期,当整体消失,部分也就随之消失。比如张三的电脑被偷了,那么电脑的所有组件也不存在了,除非张三事先把一些电脑的组件(比如硬盘和内存)拆了下来。

例子1:比如小王的自行车被偷了,那么自行车的所有组件也不存在了,除非小王事先碰巧把一些可拆卸的组件(比如车铃和坐垫)拆了下来。

public class Bicycle{
        private Bell bell;
        public Bell getBell(){
            return bell;
        }
        public void setBell(Bell bell){
            this.bell=bell;
        }
        /** 发出铃声 */
        public void alert(){
            bell.ring();
        }
    }

在Bicycle类中定义了Bell类型的成员变量,Bicycle类利用自身的bell成员变量来发出铃声,这和在Person类中定义了Bicycle类型的成员变量,Person类利用自身的bicycle成员变量去上班很相似。

例子2:我的家和我之间具有着一种强烈的所属关系,我的家是可以分享的,而这里的分享又可以有两种。其一是聚合间的分享,这正如你和你媳妇儿都对这个家有着同样的强烈关联;其二是聚合与关联的分享,如果你的朋友来家里吃个便饭,估计你不会给他配一把钥匙。

 
5.3 组合:

意义:" ... is a part of ..."

UML表示法:实心菱形 + 实线 + 箭头;箭头指向部分对象

组合是强版本的聚合,也是类间关系当中的最强版本,它直接要求包含对象对被包含对象的拥有以及包含对象与被包含对象生命期的关系。组合关系就是整体与部分的关系,部分属于整体,整体不存在,部分一定不存在,然而部分不存在整体是可以存在的,说的更明确一些就是部分必须创生于整体创生之后,而销毁于整体销毁之前。

组合关系中的部分对象在整体对象的生命期内可以被其它对象关联甚至聚合,但有一点必须注意,一旦部分所属于的整体销毁了,那么与之关联的对象中的引用就会成为空引用,这一点可以利用程序来保障。

UML中,聚合和组合是两个十分相似的概念。聚合是一种特殊的关联,聚合对象由部分对象组成;组合又是一种特殊的聚集。在一个组合对象中,部分对象只能作为组成对象的一部分与组合对象同时存在。

即是说,组合是“当聚集对象和它的组成对象之间是具有强关联的一种特殊聚集”,组合对象的关键特征是部分对象只能存在于组合对象之中,并且部分体的寿命可能比组合体短,但组合体消亡,部分体也必然消亡。

比如聚集,强调的是整体-部分关联,比如家用计算机系统PC,由主机,键盘,鼠标,显示器,声卡等组成;而组合则是强类型的聚集,聚集中的每个部分只能属于一个整体,如桌子,由桌面和桌腿组成,没有桌面的桌子和没有桌腿的桌子都不能称为是桌子,这个是区别。

例子1:心脏的生命期与人的生命期是一致的,换个部分就不一定了,比如阑尾,很多人在创生后的某个时间对其厌倦便提前销毁了它,可它和人类的关系不可辩驳的属于组合。

在UML中存在一种特例,就是允许被包含对象在包含对象销毁前转移给新的对象,这虽然不自然,但它给需要心脏移植的患者带来了福音。

【参考文献】

1 floodpeak 类间的关系:http://www.cnblogs.com/floodpeak/archive/2008/11/30/1083533.html

2 Anytao [从设计到架构]第四回:依赖的哲学(上):http://www.cnblogs.com/anytao/archive/2008/12/02/anytao_design_04_couplingphilosophy.html

3 孙卫琴 《Java面向对象编程》一书中的“区分关联、依赖和聚集关系” http://www.javathinker.org/main.jsp?bc=showessay.jsp&filename=java/qufen.htm


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/chevydream/archive/2008/12/24/3594361.aspx

原文地址:https://www.cnblogs.com/zxjyuan/p/1633773.html