用途:该技能用于在设计与重构类层次结构时,判断应当使用继承还是组合,并可审查那些通过重写方法禁用父类行为的子类。
主要能力:
- 强调组合优于继承这一 GoF《设计模式》(1994)中的面向对象设计原则,指出继承是白盒复用,父类内部实现被子类可见并依赖,会破坏封装;组合是通过良好定义接口的黑盒复用,且可在运行时重新配置,而 extends 子句在编译期固定。默认方案是 has-a 加接口,继承应为有意的例外。
- 解释脆弱基类问题:子类不仅依赖父类做什么,还依赖父类如何调用自身;重写一个方法会使其他调用它的方法行为一并改变,父类在后续版本中新增的方法也会影响子类,因此基类可在不更改已发布契约的情况下破坏子类。
- 指出继承用于多态而非代码复用:继承捆绑了子类型多态与实现复用两种能力,只有当需要在调用点可被替换时才应使用子类化;若只想要方法体,应使用组合,仅为获取代码而继承会意外签署可替换性契约。
- 说明 is-a 测试应按 Liskov 行为子类型进行,而非词义匹配:子类不得加强前置条件、削弱后置条件或破坏父类不变式;经典陷阱是正方形词义上 is-a 矩形,但 Square 子类会破坏 Rectangle 关于宽高独立变化的隐含保证,重写方法抛出 UnsupportedOperationException 或改为空操作即表明 is-a 不成立。
- 提供无耦合抽象的方式:通过接口定义契约并在构造时注入实现,可在保留多态的同时去掉实现耦合。文中列举了 Java/C#/TypeScript 用 interface,Python 用 Protocol(结构化)或 ABC,Go 用隐式接口与 struct embedding,Rust 用 traits,Kotlin 用 interface 与类委托 by,Swift 用 protocols 与 protocol extensions。
- 提供快速检查清单:为复用代码而非为被替换而继承,应改为组合加转发;父类若改变自身方法调用方式就会破坏子类,属于脆弱基类,应组合;is-a 需在行为层面成立,而非仅词义成立;需要契约但不想要代码时,使用接口加构造注入实现;若属于框架钩子、密封变体集或异常类型,继承仍然合适。
适用场景:设计或重构类层次结构、在继承与包装之间做选择、代码审查中识别仅为复用代码的继承,以及评审违规子类。
限制:资料未提供具体安装方式、命令或示例代码,相关细节暂未提供。