mcpField
C
composition-over-inheritance

composition-over-inheritance

github.com/yanko-belov开发1341 次安装40 周前更新
代码仓库

关于 composition-over-inheritance

面向开发者的软件设计技能,帮助选择类行为的组织方式。它建议通过接口组合能力,而非依赖继承,同时指出继承仍适用的有限情形。

主要功能

  1. 01优先使用组合而非继承
  2. 02通过接口组合不同能力
  3. 03识别继承设计的警示信号
  4. 04在有限类型层级中使用继承

适用场景

  1. 01设计具有多种行为的类
  2. 02重新审视生硬或过深的类层级
  3. 03解耦行为以便于测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yanko-belov/code-craft --skill composition-over-inheritance

安装后在 Claude Code 中输入/composition-over-inheritance确认已加载。

详细介绍

组合优于继承

概述

优先使用对象组合,而非类继承。

继承会造成紧密耦合和僵化的层级结构。组合则能创建灵活、可复用的组件,并可自由搭配。

适用场景

  • 设计类之间的关系
  • 想要使用 extends 时
  • 类需要来自多个来源的行为时
  • 创建“是一个”关系时
  • 构建类层级时

铁律

NEVER use inheritance when composition would work.

没有例外:

  • 不能因为“这是面向对象编程的做法”
  • 不能因为“这是‘是一个’关系”
  • 不能为了“通过 extends 复用代码”
  • 不能为了“多态”

默认使用组合。只有真正的类型层级才使用继承。

检测:继承异味

如果继承让人觉得别扭或牵强,就使用组合:

// ❌ VIOLATION: Inheritance hierarchy
class Animal {
  eat(): void { console.log('Eating'); }
}

class FlyingAnimal extends Animal {
  fly(): void { console.log('Flying'); }
}

class SwimmingAnimal extends Animal {
  swim(): void { console.log('Swimming'); }
}

// Duck needs both fly AND swim - inheritance can't do this cleanly
class Duck extends FlyingAnimal {
  swim(): void { console.log('Swimming'); } // Duplicated!
}

正确模式:使用接口进行组合

将能力定义为接口,并进行组合:

// ✅ CORRECT: Composition
interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

interface Eatable {
  eat(): void;
}

// Reusable behaviors
const flyingBehavior: Flyable = {
  fly() { console.log('Flying'); }
};

const swimmingBehavior: Swimmable = {
  swim() { console.log('Swimming'); }
};

const eatingBehavior: Eatable = {
  eat() { console.log('Eating'); }
};

// Compose what you need
class Duck implements Flyable, Swimmable, Eatable {
  fly = flyingBehavior.fly;
  swim = swimmingBehavior.swim;
  eat = eatingBehavior.eat;
}

class Fish implements Swimmable, Eatable {
  swim = swimmingBehavior.swim;
  eat = eatingBehavior.eat;
}

class Bird implements Flyable, Eatable {
  fly = flyingBehavior.fly;
  eat = eatingBehavior.eat;
}

继承为何会失败

问题示例
菱形问题鸭子需要飞行和游泳能力
紧密耦合子类了解父类的内部细节
僵化的层级结构无法更改父类而不破坏子类
强制继承获得自己不需要的方法
脆弱的基类父类的更改会破坏所有子类

组合为何更胜一筹

优点示例
灵活可组合任意行为
松散耦合组件彼此互不知晓
易于测试可单独模拟各项行为
运行时更改可动态替换行为
不受层级结构限制可自由添加新的组合

抵御压力的流程

1. “这是面向对象编程的做法”

压力:“面向对象编程使用继承。”

**回应:**现代面向对象编程更倾向于组合。继承被过度使用了。

**行动:**使用接口和组合。这仍然是面向对象编程。

2. “这是‘是一个’关系”

压力:“鸭子是鸟,所以它应该继承鸟。”

**回应:**需求变化后,“是一个”往往会变成“有一个”。组合可以应对这两种情况。

**行动:**将其建模为“具有某些行为”,而不是“属于某种类型”。

3. “通过 extends 复用代码”

压力:“我需要父类的方法。”

**回应:**组合可以更好地复用代码,同时避免耦合。

**行动:**将共享行为提取为可组合的单元。

4. “多态需要继承”

压力:“我需要统一处理不同类型。”

**回应:**接口无需继承即可提供多态。

**行动:**定义接口,并让类实现它。

危险信号——停止并重新考虑

如果注意到以下任何一点,请改用组合:

  • 代码中出现 extends 关键字
  • 类层级超过 2 层
  • 子类重写父类方法
  • “菱形问题”——需要多个父类
  • 子类没有使用父类的所有方法
  • 更改父类会破坏子类
  • 不实例化父类就难以测试

以上情况都意味着:重构为组合。

适合使用继承的场景

仅在以下情况下使用继承:

  • 真正的类型层级(很少见)
  • 框架要求这样做(如 React 类组件等)
  • 扩展由你无法控制的库类

即便如此,也要保持层级结构扁平(最多 2 层)。

快速参考

继承组合
class Dog extends Animalclass Dog implements Animal + 注入行为
僵化的层级结构灵活的组合
只能有一个父类多种行为
紧密耦合松散耦合
更改会层层蔓延更改彼此隔离

常见借口(都不成立)

借口事实
“这是面向对象编程的做法”现代面向对象编程更倾向于组合。
“这是‘是一个’关系”“具有某种行为”更灵活。
“需要父类的方法”改为组合相应行为。
“多态需要它”接口可以提供多态。
“用 extends 代码更少”组合更灵活。
“我已经注明它有问题”如果它有问题,就不要这样做。

核心要点

组合行为,而非继承行为。

设计类时:为各种能力定义接口,创建可组合的行为,并注入每个类所需的内容。仅在万不得已时使用 extends。

评论

支持 Markdown