美文网首页
2022-09-20 里氏替换

2022-09-20 里氏替换

作者: 三流之路 | 来源:发表于2022-09-20 09:52 被阅读0次

    继续阅读《设计模式之禅》第二章——里氏替换原则,英文 Liskov Substiution Principle,简称 LSP,查了下这个 Liskov,全名 Barbara Liskov,是位大佬。

    含义是父类能出现的地方,子类就可以出现,不会引起任何错误或异常。当然子类出现的地方,不要求父类能出现。

    直觉上子类是拓展了父类,父类能做的事确实子类都应该能做到,子类可能有自己独有的东西,父类无法实现。

    1. 定义时尽量用父类或接口,这样使用时可以传递各种不同实现的子类。

    2. 在使用父类定义的逻辑中,如果对于某个子类需要单独处理,那就不要让它成为子类。

      如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。`

      书中举的例子是士兵可以用各种枪支杀敌,但传过来一把玩具枪就歇菜了,逻辑里需要单独判断类是不是玩具枪,这种写法打破了原有的逻辑,一种特例判断,不好。索性玩具枪自己单独一个类,没有依赖关系。

    3. 使用特定子类的地方,不一定还能用父类,比如需要一个房子睡觉,房子有子类别墅大三居地下室隔断,给人配房子那可以配各种子类,都能用来睡觉,但如果配的就是 隔断,直接传房子就不行了,像我这种只能住10平方隔断的人,你塞来一个房子,那租金是1000还是10000,谁也保证不了,所以我明确就要住隔断就请只给我传递隔断,当然传递隔断的子类就又符合 LSP 了,比如传个暗隔明隔实隔等等。

    4. 子类输入参数范围可以扩展

      比如父类的方法参数是 HashMap,子类同名方法参数是 Map,方法名相同,参数类型不同,属于重载。

      这样父类调用传一个 HashMap 参数,父类方法被执行,根据 LSP 原则,父类可以被替换为子类,还是执行子类从父类继承来的方法,即执行那个参数是 HashMap 的方法。

      这是希望的。如果父类参数是 Map,子类是 HashMap 相当于范围缩小,这样父类传参 HashMap,执行到参数是 Map 的方法,当父类被子类替换后,会执行到子类参数是 HashMap 的方法,而不是从父类继承的参数为 Map 的方法。

      这是不希望的,因为进入了子类自己的方法,根据 LSP 有父类的地方就可以用子类,本来是父类的通用逻辑,但是这样走子类却走入了子类特有逻辑。比如

      // 父类
      public class Person {
          public void eat(Breakfast bf) {
              print("太好吃了")
          }
      }
      // 子类
      public class Star extends Person {
          public void eat(Breakfast50 bf) {
              print("650的早餐都不够?50元,你喂狗的吗")
          }
      }
      
      // 我
      Person programmer = new Person();
      // 苏老师
      Star star = new Star();
      // 50元的豪华早餐
      Breakfast50 bf50 = new Breakfast50();
      // 输出“太好吃了”,领导很开心,工人们还是很容易满足的嘛
      programmer.eat(bf50);
      // 输出“650的早餐都不够?50元,你喂狗的吗“,领导脸黑了,我正在弘扬节约精神呢,你这狗东西敢打我脸
      star.eat(bf50);
      

      上面的打脸原因在于子类缩小了参数范围,在可以使用父类的地方改用子类就执行了子类特有的内容,引起了不适。如果反过来

      public class Person {
          public void eat(Breakfast50 bf) {
              print("一顿早餐就50,也太豪华了吧")
          }
      }
      // 子类
      public class Star extends Person {
          public void eat(Breakfast bf) {
              print("你这早餐多少钱,没1000就赶紧给我滚开")
          }
      }
      Person programmer = new Person();
      Star star = new Star();
      Breakfast50 bf50 = new Breakfast50();
      programmer.eat(bf50); // 输出"一顿早餐就50,也太豪华了吧"
      star(bf50); // 输出"一顿早餐就50,也太豪华了吧"
      

      无论什么人,吃 50 元早餐都很满足,你看贫富分化也看不出来了,社会也和谐了,领导也开心了。

    5. 子类返回值范围可以被缩小

      重写时返回值必须是父类返回值的子类。重载时按照上面一条规则,如果参数是父类定义的参数类型,不会执行到子类,子类返回值也无所谓。

    书里说最佳实践是尽量减少子类个性,但子类不就是要拥有各自的个性吗?把 Star 当普通 Person 去 eat 普通的 Breakfast,确实委屈了 Star,但为了隐藏贫富差距的矛盾,就得这么用,除非 Star 别继承 Person。

    相关文章

      网友评论

          本文标题:2022-09-20 里氏替换

          本文链接:https://www.haomeiwen.com/subject/rvybortx.html