继承与复合的选择

543 阅读2分钟

继承与复合

继承与复合都可以在一定程度上对类进行扩展。

继承

假设,B类继承自A类,B类是A类的派生类,子类B具有A类的某些特性。那么可以说,A类和B类是同一种东西,也就能使用is-a来表示两者的关系-----继承。

继承分为接口继承和实现继承,两者主要的目标是代码重用。

举个例子

车具有引擎和车轮,奔驰车也具有引擎和车轮,同时奔驰车具备基础车不具备的Boss音响,即便如此,它仍然是车,因为它具备基础车所有的属性,只是在基础车上增加了Boss音响。用英语表示两者的关系是:BenzCar is Car.

使用XML图表示两者的关系:

image-20211226170641787

@Data
public class Car {
    /**
     * 引擎
     */
    private String engine;
    /**
     * 轮子
     */
    private String wheel;
}
public class BenzCar extends Car{
    private String bossSound;
}

复合

假设,B类需要引用A类的某些属性和功能,B类只需要A类的属性,并不需要暴露/知道A类的具体细节,当需要获取A类的属性或数据时,仅需要暴露A类的API提供访问就可以,我们认为,A类和B类不是同一种东西,B类只是需要A类的部分信息,也就能使用has-a来表示两者的关系-----复合。

B类的任何一个实例对象都可以调用现有方法来返回A类的结果,这种方式叫转发。

同样举个例子

车库里有各种各样的灯和不同的车,车库并不关心车库内有什么品牌的车,车本质上并不是车库的一部分,而是只需要实现停车的功能或者需要灯具实现照亮车库的功能,那么我们只需要在车库中包含车辆、灯具的实例就可以实现。

使用XML图表示两者的关系:

image-20211226172200703

@Data
public class Opple {
    /**
     * 灯
     */
    private String light;
}
@Data
public class YeeLight {
    /**
     * 灯
     */
    private String light;
}
@Data
public class ParkLot {
    /**
     * 停车场需要欧普的灯实现照亮功能
     */
    private Opple opple;
    /**
     * 停车场需要yeeLight的灯实现照亮功能
     */
    private YeeLight yeeLight;
    /**
     * 停车场里有车,实现停车功能
     */
    private Car car;
}

Tips

无论继承还是复合,两者实质上都是对原有类进行扩展,只不过是方式不同、适用场景不同,两者XML合在一起长这样:

image-20211226172359142

但是,我们在扩展类时

需要考虑的点: 两者是is-a是与否的关系还是has-a包含关系?

需要遵循的原则是: 复合优先于继承。

需要明确的点是: 继承破坏了封装性,子类依赖于父类特定的功能和细节,但是父类在迭代中,子类可能会遭到破坏,可能导致子类出现坏结果。