类之间的关系
最常见的类之间的关系:
- 继承(is a)
- 聚合(has a)
- 依赖(uses a)
继承 is a
继承不仅继承了父类的属性和方法,还继承了种类,与父类形成“是一种”的关系,即“is a”的关系。
所有的子类“是一种”父类。喜鹊“是一种”鸟,麻雀也“是一种”鸟。
聚合 has a
聚合较好理解,一个对象包含另一个对象,如:手机类聚合相机类,很合理的,手机一般内置了拍照功能,手机对象自然而然包含相机对象。
显然,这里手机是“有一个”相机(功能),而不是“是一种”相机了。
注:聚合通常也会被称为关联or复合!
依赖 uses a
依赖最好理解。类A的一个方法必须依赖另一个类的对象才能实现想要的效果。
和聚合比较,细节至代码级的差别一般表现为:
聚合是类A将类B聚合为自己的一个属性,所有类A的所有对象天生拥有类B对象;
依赖是类A的方法操作类B的对象,要想完成实现这个方法,要用到类B对象。
常用类关系的UML符号
更合理的架构
避免滥用继承,思考类的关系是“是一种”or“有一个”,优先考虑利用聚合关系(“有一个”),适当的时候再用上继承,是设计更有弹性。
举个栗子
有三个类:
- 相机类:Camera.java
- 手机类:Phone.java
- 音乐手机类:MusicPhone.java
其中,手机肯定是内置相机功能的,所以手机“有一种”相机;而音乐手机肯定“是一种”手机。类之间聚合和继承的关系这样较为合理。
反之,如果为了手机有相机的功能而滥用继承,即让手机继承相机,显然不合常理。
错误地设计
相机类:Camera.java
/**
* 照相机类,显然只有照相功能
*/
public class Camera {
public void takephoto() {
System.out.println("拍照");
}
}
手机类:Phone.java
/**
* 手机类,显然有照相功能,这里利用不合理的继承,is a 关系
*/
public class Phone extends Camera{
public void dial() {
System.out.println("打电话");
}
}
音乐手机类:MusicPhone.java
/**
* 音乐手机类,显然它本身是个手机,继承手机类 is a
*/
public class MusicPhone extends Phone{
public void play() {
System.out.println("播放MP3");
}
}
测试运行
/**
* 测试不合理的继承:
* MusicPhone -> Phone -> Camera
* 尽管可以实现功能,即音乐手机对象同时具备音乐播放、打电话和拍照功能,and 手机对象有打电话和相机功能,都是继承得到的。
* 但是 Phone 应该不是Camera, 应该考虑聚合 has a
*/
public class Test {
public static void main(String[] args) {
MusicPhone cellphoneWithMP3 = new MusicPhone();
cellphoneWithMP3.dial();
cellphoneWithMP3.takephoto();
cellphoneWithMP3.play();
}
}
结果:
打电话
拍照
播放MP3
正确的设计
主要改变Phone和Camera之间的关系。
相机类:Camera.java,不变
手机类:Phone2.java ,不再继承相机,而是聚合
/**
* 手机应该聚合相机,手机是拥有照相功能,而不是属于相机,手机has a 相机
*/
public class Phone2 extends Camera{
public void dial() {
System.out.println("打电话");
}
}
音乐手机类:MusicPhone2.java
/**
* 音乐手机类,显然它本身是个手机,继承手机类 is a
* 这里可以继承,也可以聚合。因为音乐手机属于手机没毛病
*/
public class MusicPhone extends Phone{
public void play() {
System.out.println("播放MP3");
}
}
测试运行
/**
* 测试不合理的继承:
* 纠正不合理的继承:
* 手机聚合相机,音乐手机继承手机
*/
public class Test2 {
public static void main(String[] args) {
MusicPhone2 musicCellphone2 = new MusicPhone2();
musicCellphone2.play();
musicCellphone2.dial();
//musicCellphone2.takephoto(); //这样会报错,因为手机不在是相机,而是聚合相机
musicCellphone2.camera.takephoto();//注意这里的变化
}
}
结果:
打电话
拍照
播放MP3
简单架构图
最后
参考:Java核心技术卷一 -类之间的关系 和 良葛格的openhomecc