组合优于继承
只用继承抽象基类会带来什么问题?
- 问题: 基类把全部行为声明为抽象方法后, 子类被迫实现自己并不具备的行为;
- 表现: 子类需要改写或用空实现覆盖这些方法;
- 后果: 复用性下降, 同时对外暴露了不应该存在的接口;
abstract class AbstractBird {
public abstract fly(): void;
public abstract eat(): void;
}
class TuoNiao extends AbstractBird {
public fly(): void {
// 鸵鸟不会飞, 只能空实现或抛错
}
public eat(): void {
// ...
}
}
只用接口实现组合缺什么?
- 做法: 对象按自身行为选择接口实现, 不再继承无用的抽象基类;
- 结果: 接口污染消失, 但接口里没有逻辑, 公共实现无法复用;
interface IFlyAble {
fly(): void;
}
interface IEatAble {
eat(): void;
}
class TuoNiao implements IEatAble {
eat(): void {
// 逻辑只能逐个类重复实现
}
}
如何用接口加委托同时获得组合与复用?
- 思路: 接口只声明能力, 能力的实现放进独立的委托类;
- 组合: 对象按需持有委托对象作为属性, 在自己的方法里调用委托对象;
- 收益: 公共逻辑复用一次, 私有逻辑由对象自己补充;
// 委托: 把可复用逻辑抽成独立对象
class EatAbility {
eat() {
// 公共逻辑实现
}
}
interface IEatAble {
eat(): void;
}
class MaQue implements IEatAble {
private _eatAbility = new EatAbility();
eat(): void {
this._eatAbility.eat(); // 复用委托逻辑
// 马雀私有的补充逻辑
}
}
委托的粒度应如何选择?
- 粒度: 一个委托类承载一组内聚的行为, 不要把无关行为塞进同一个委托;
- 判断: 若某些行为总是一起变化, 归入同一个委托; 独立变化的行为拆成不同委托;
- 影响: 粒度越细组合越灵活, 但对象与转发调用也越多;