类之间的关系大体上存在五种—继承(实现)、依赖、关联、聚合、组合。
继承
继承是一种“is-a”关系。多了就不说了,都懂得。
依赖
依赖简单的理解,就是一个类A中的方法使用到了另一个类B。
这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。
比如说,我用笔写字,首先需要一个类来代表我自己,然后需要一个类来代表一支笔,最后,‘我’要调用‘笔’里的方法来写字,用代码实现一下:
1 | public class Pen { |
看到这大家都懂了,因为这种代码你每天都会写。现在你知道了,这就是一种类与类之间的关系,叫做依赖。
这种关系是一种很弱的关系,但是pen类的改变,有可能会影响到Me类的结果,比如我把pen类write方法的方法体改了,me中再调用就会得到不同的结果。
一般而言,依赖关系在Java中体现为局域变量、方法的形参,或者对静态方法的调用。
关联
关联体现的是两个类、或者类与接口之间语义级别的一种强依赖关系。
这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的。
1 | // pen 还是上面的pen |
被关联类B以类属性的形式出现在关联类A中,或者关联类A引用了一个类型为被关联类B的全局变量的这种关系,就叫关联关系。
在Java中,关联关系一般使用成员变量来实现。
聚合
聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系
看下面一段代码:
1 | public class Family { |
在代码层面,聚合和关联关系是一致的,只能从语义级别来区分。普通的关联关系中,a类和b类没有必然的联系,而聚合中,需要b类是a类的一部分,是一种”has-a“的关系,即 a has-a b; 比如家庭有孩子,屋子里有空调。
但是,has 不是 must has,a可以有b,也可以没有。a是整体,b是部分,整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。
不同于关联关系的平等地位,聚合关系中两个类的地位是不平等。
组合
组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。
1 | public class Person { |
组合同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束。
就像你有鼻子有眼睛,如果你一不小心结束了生命周期,鼻子和眼睛的生命周期也会结束,而且,鼻子和眼睛不能脱离你单独存在。
只看代码,你是无法区分关联,聚合和组合的,具体是哪一种关系,只能从语义级别来区分。
同样,组合关系中,两个类关系也是不平等的。
组合,聚合和继承
依赖关系是每一个java程序都离不开的,所以就不单独讨论了,普通的关联关系也没有什么特殊的地方,下面我们重点研究一下组合,聚合和继承。
聚合与组合
聚合与组合都是一种关联关系,只是额外具有整体-部分的意义。
部件的生命周期不同
聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。
组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时间共享同一个部件。
这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。
聚合关系是【has-a】关系,组合关系是【contains-a】关系
平时我们只讨论组合和继承的时候,认为组合是【has-a 】关系,而事实上,聚合才是真正的【has-a】关系,组合是更深层次的【contains-a】关系。
由于【contains-a】关系是一种更深的【has-a】关系,所以说组合是【has-a】关系也是正确的。
组合和继承
这个才是本文的重点。
学过设计模式的都知道,要“少用继承,多用组合”,这究竟是为什么呢?
我们先来看一下组合和继承各自的优缺点:
组合
优点:
- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
- 具有较好的可扩展性
- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
- 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口
缺点:
- 整体类不能自动获得和局部类同样的接口
- 创建整体类的对象时,需要创建所有局部类的对象
继承
优点:
- 子类能自动继承父类的接口
- 创建子类的对象时,无须创建父类的对象
缺点:
- 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
- 支持扩展,但是往往以增加系统结构的复杂度为代价
- 不支持动态继承。在运行时,子类无法选择不同的父类
- 子类不能改变父类的接口
(后面罗里吧嗦的,不记了)