跳到主要内容

组合优于继承

只用继承抽象基类会带来什么问题?​

  • 问题: 基类把全部行为声明为抽象方法后, 子类被迫实现自己并不具备的行为;
  • 表现: 子类需要改写或用空实现覆盖这些方法;
  • 后果: 复用性下降, 同时对外暴露了不应该存在的接口;
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(); // 复用委托逻辑
// 马雀私有的补充逻辑
}
}

委托的粒度应如何选择?​

  • 粒度: 一个委托类承载一组内聚的行为, 不要把无关行为塞进同一个委托;
  • 判断: 若某些行为总是一起变化, 归入同一个委托; 独立变化的行为拆成不同委托;
  • 影响: 粒度越细组合越灵活, 但对象与转发调用也越多;