10 LSP: The interface segregation principle

接口隔离原则(ISP)

图10.1 接口隔离原则
在图10.1描述的场景中,有多个使用者会调用 OPS 类中的操作。假设:

  • User1 只使用 op1
  • User2 只使用 op2
  • User3 只使用 op3

现在设想 OPS 是一个用 Java 这类语言编写的类。显然,在这种情况下,User1 的源代码会非本意地依赖 op2 和 op3,即便它根本不会调用这两个方法。
这种依赖意味着:如果 OPS 中 op2 的代码发生修改,即便 User1 关心的内容没有任何变化,也会强制 User1 重新编译和重新部署

这个问题可以通过将操作隔离到不同接口来解决,如图10.2所示。
同样以 Java 这类静态类型语言为例,User1 的源代码将只依赖 U1Ops 接口和 op1 方法,而不再依赖 OPS 类。因此,OPS 中任何 User1 不关心的改动,都不会导致 User1 重新编译和重新部署。

ISP 与编程语言

显然,上述描述严重依赖于语言的类型系统。
像 Java 这样的静态类型语言,会强制程序员编写使用者必须导入、引用或包含的声明。正是源代码中这些被引入的声明,形成了会强制触发重新编译与重新部署的源码依赖。

而在 Ruby、Python 这类动态类型语言中,源代码里不存在这类声明,类型信息是在运行时推断的。因此,不会存在会导致强制重新编译与重新部署的源码依赖。
这也是动态类型语言构建的系统比静态类型语言更灵活、耦合度更低的主要原因。

这一事实可能会让你认为:ISP 是一个语言层面问题,而非架构层面问题。

ISP 与系统架构

如果退一步审视 ISP 的根本动机,你会发现背后潜藏着更深层的关注点。
一般来说,依赖包含了你不需要内容的模块是有害的
这一点对于会导致不必要重新编译、重新部署的源码依赖显然成立——在更高、更宏观的架构层面同样成立。

举个例子:某位架构师正在开发系统 S,他希望将某个框架 F 引入系统。
假设框架 F 的开发者将其与某个特定数据库 D 绑定。于是:
S 依赖 F,F 又依赖 D(如图10.3)。

图10.3 存在问题的架构

现在假设 D 包含了 F 不会使用、进而 S 也不关心的功能。
D 中这些功能的变更,很可能会强制 F 重新部署,进而强制 S 重新部署。
更糟的是,D 中某个功能出现故障,可能会引发 F 乃至 S 整体失效。

结论

这里的教训是:依赖一个附带了你不需要的“冗余包袱”的组件,会给你带来意料之外的麻烦。

posted @ 2026-03-19 18:31  cyusouyiku  阅读(5)  评论(0)    收藏  举报