类之间的关系大体上存在五种:继承(实现)、依赖、关联、聚合、组合。
这其中聚合和组合都是关联的一种特列。
继承:对于类来说,这种关系叫做继承,而对于接口来说,这种关系叫做实现。继承是一种“is-a”关系。
依赖:简单的理解,就是一个类A中的方法使用到了另一个类B。这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到类A。
比如说,我想打篮球,首先需要一个类来代表我自己,然后需要一个类来代表一一个篮球,最后,‘我’要调用‘篮球’里的方法来玩耍,用代码实现一下:
public class Ball {
public void play(){
System.out.println("use ball to play");
}
}
public class Me {
public void play(Ball ball){//这里,play作为Me类方法的参数。 Me类依赖Ball类
ball.play();
}
}
这就是一种类与类之间的关系,叫做依赖。
一般而言,依赖关系在Java中体现为局域变量、方法的形参,或者对静态方法的调用。
关联:被关联的类以类属性的形式出现在关联的类中,或者关联类A引用了一个类型为被关联类B的全局变量的这种关系,就叫关联关系。
体现的是两个类、或者类与接口之间语义级别的一种强依赖关系。这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的。
// Ball还是上面的Ball
public class You {
private Ball ball; // 让Ball成为You的类属性,这就是关联
public You(Ball ball){
this.ball = ball;
}
public void play(){
ball.play();
}
}
在Java中,关联关系一般使用成员变量来实现。
聚合:是关联关系的一种特例,他体现的是整体与部分、拥有的关系,整体与部分是可分的,即has-a的关系
public class Family {
private List<Child> children; //一个家庭里有许多孩子
// ...
}
在代码层面,聚合和关联关系是一致的,只能从语义级别来区分。普通的关联关系中,a类和b类没有必然的联系,而聚合中,需要b类是a类的一部分,是一种”has-a“的关系,即 a has-a b; 比如家庭有孩子,屋子里有空调。但是,has 不是 must has,a可以有b,也可以没有。
不同于关联关系的平等地位,聚合关系中两个类的地位是不平等。
组合:是关联关系的一种特例,他体现的是一种contains-a的关系,整体与部分是不可分的,关系比聚合更强,也称为强聚合。
public class Person {
private Eye eye = new Eye(); //一个人有鼻子有眼睛
// ....
}
组合同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束。
同样,组合关系中,两个类关系也是不平等的。
几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖;
总结
学过设计模式的都知道,要“少用继承,多用组合”,这究竟是为什么呢?
组合与继承的区别和联系
在继承结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种 白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性)
组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用 。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
继承在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。
组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。
当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。
引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活”。从这句话可以看出组合的灵活性比继承高,这应该是“少用继承,多用组合”的原因吧。
所以建议在同样可行的情况下,优先使用组合而不是继承。