里氏替换原则(LSP)如何应用于函数返回类型?是否与参数规则相反?
里氏替换原则在函数返回类型中的应用
当然,里氏替换原则(LSP)完全适用于函数返回类型,而且规则确实和输入参数的情况相反。咱们一步步理清楚:
先快速回顾下输入参数的逻辑:我们用最底层的抽象类型(比如Iterable<T>而非List<T>),这样函数就能接受该抽象的所有子类型。比如foo函数接受Iterable<Any>的话,传入List<Any>、MutableList<Any>甚至自定义的Iterable实现都没问题——这完全符合LSP,因为子类型可以安全地替换父类型作为输入,不会破坏程序正确性。
到了返回类型这里,逻辑就反过来了:我们应该优先返回能满足调用方需求的最抽象类型(所以你的例子里,fun bar(): Iterable<Any>才是更优的写法),原因如下:
- 实现灵活性更高:如果定义返回
Iterable<Any>,你后续可以随意更换底层实现——今天返回List<Any>,明天换成Set<Any>,甚至自定义的Iterable实现都可以,而调用方的代码完全不需要修改,因为他们只依赖Iterable接口定义的方法(比如getIterator())。 - 严格遵循LSP要求:任何
Iterable的子类型都可以作为返回值,不会违背调用方的预期。反过来,如果定义bar()返回List<Any>,你就被限制死了:以后不能返回其他Iterable子类型(比如Set),因为调用方可能会依赖List特有的方法(比如get(index))。更糟的是,如果这个函数是父类的方法,子类重写时必须返回List或其子类型——根本不能返回更通用的Iterable,否则会因为调用方的依赖导致程序出错,直接违反LSP。
用你给出的接口举个具体例子:
- 如果
bar()返回Iterable<Any>,调用方只会调用getIterator()方法,不管实际返回的是List还是MutableList,这个调用都能正常工作; - 如果
bar()返回List<Any>,调用方可能会调用get(index)或者访问size属性,要是你之后把实现改成返回非List的Iterable,这些调用就会直接失败——这就违反了LSP,因为返回类型的替换破坏了程序正确性。
最后总结下规则:
- 输入参数:尽量接受最通用(抽象)的类型,兼容所有子类型;
- 返回类型:尽量返回最通用(抽象)的类型,方便后续替换任意子类型而不影响调用方。
内容的提问来源于stack exchange,提问作者Matthew Layton
相关产品推荐
相关产品推荐

