2022-09-20 里氏替换

2022-09-30 08:54:35 浏览数 (1)

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

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

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

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

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

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

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

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

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

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

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

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

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

代码语言:javascript复制
// 父类
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();
// 输出“太好吃了”,领导很开心,工人们还是很容易满足的嘛
worker.eat(bf50);
// 输出“650的早餐都不够?50元,你喂狗的吗“,领导脸黑了,我正在弘扬节约精神呢,你这狗东西敢打我脸
star.eat(bf50);

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

代码语言:javascript复制
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 元早餐都很满足,你看贫富分化也看不出来了,社会也和谐了,领导也开心了。

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

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

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

0 人点赞