什么是里氏替换原则?
1、如果对每一个类型为 S 的对象O1,都有类型为 T 的对象O2,使得以 T 定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
2、所有引用基类的地方必须能透明地使用其子类的对象
概述
继承时OOP 的三大特点与优势之一,通过使用可以使相关类间的层次结构关系更加清晰,能有效的提高代码的重要性和扩展性。但是如果滥用继承反而会适得其反,不仅仅增加了父子类之间的耦合性,还会降低子类代码的灵活性。因为父类的属性和方法对子类具有侵入性的,同时如果修改了父类的属性或方法,将会影响其所有子类功能的运行。在以面向对象方式编程时,继承的运用是必不可少的,因为它是非常优秀的语言机制,优点如下:
- 代码共享,减少创建类的工作量,每个子类都自动继承了父类的方法和属性。
- 有效提高代码的重用性与扩展性。
- 子类形似父类,但是有可以异于父类,因为子类不但拥有父类的功能,还可以额外添加属于自己的功能。
但事物都是具有两面性的,继承除了有上述的优点外,也有如下几个方面的缺点:
- 继承是具有侵入性的,也就是只要子类使用了继承,就必须拥有父类的所有方法和属性,就降低了代码的灵活性,让子类有了一些约束。
- 代码的耦合性增加,当父类中的常量,变量,方法被修改时,就需要考虑子类的修改。这种修改可能会带来代码大量重构的糟糕的结果。
在编码过程中引入里氏替换原则,就可以在使用继承的过程扬长避短。
示例代码
在类 T 中定义 foo,bar,在类 S 中定义方法 talk。
class T(object):
def foo(self):
print("this is foo")
def bar(self):
print("this is bar")
class S(T):
def talk(self):
print("i am talking")
if __name__ == "__main__":
o2 = T()
o1 = S()
o1.foo() # 由于 foo 方法是在父类 T 中定义的,即便 o1 类 S 中没有有 foo 也可以输出结果,引用基类(foo)的地方可以透明的使用子类的实例 o1。
o1.bar() # 由于 foo 方法是在父类 T 中定义的,即便 o1 类 S 中没有有 bar 也可以输出结果,引用基类(bar)的地方可以透明的使用子类的实例 o1。
o2.foo()
o2.bar()
o1.talk()
o2.talk() # 报错,引用子类的地方不可以透明的使用其父类,因为子类可能对父类做了扩展
里氏替换原则通俗来讲就是——子类可以扩展父类的功能,但是不能改变父类原有的功能。里氏替换原则为我们良好的使用继承提供了一个规范,其核心内涵是任何使用基类的地方都可以在不了解其子类具体实现的情况下,无条件地使用其子类替换,而不会产生任何错误或异常。
那么如果编码出符合里氏替换原则的代码呢?
子类必须完全实现父类的抽象方法,但是不可覆盖父类的非抽象方法
代码示例:
抽象类 ABUserInfo 提供了抽象方法 getName 与非抽象方法 getTitle,其中 getName 方法需要被子类 UserInfo 重写。
from abc import abstractmethod, ABCMeta
class ABUserInfo(metaclass=ABCMeta):
def getTitle(self):
return "用户信息"
@abstractmethod
def getName(self):
pass
class UserInfo(ABUserInfo):
def getName(self):
return "Tomcat"
class showUserInfo:
def __init__(self, UserInfo):
self.UserInfo = UserInfo
def show(self):
print(self.UserInfo.getTitle() + "\r\n姓名:" + self.UserInfo.getName())
if __name__ == "__main__":
showUserInfo(UserInfo()).show()
若需求发生了变化,还需要展示用户个人资料的相关信息,我们采用如下方法编码了一个新的子类 UserInfo ,它不仅实现了父类的抽象方法,还重写了父类的非抽象方法。
class UserInfo(ABUserInfo):
def getName(self):
return "Tomcat"
def getTitle(self):
return "个人资料"
if __name__ == "__main__":
showUserInfo(UserInfo()).show()
虽然代码运行正常,但是由于抽象类 ABUserInfo 是为了展示用户信息而开发的,其子类需遵从父类的约定,应该均是用户信息类。但是在使用类 ShowUserInfo 展示用户信息时,却发现展示的不是用户信息而是个人资料,所以在子类中重写抽象父类中的非抽象方法是不符合里氏替换原则规范的。
子类可以实现自身特有的方法
父类一般是由众多实体对象提取共性而抽象出来的,子类是对父类共性的个性实现,父类的共性是子类的规范同时也是一种约束,这种约束在一定程度上限制了子类的功能。在一些特殊应用场景中,子类需要打破常规,摆脱束缚,颠覆传统,彰显个性。解决方式就是在父类共性范围之外增加子类特有的方法。
代码示例:
抽象类 ABCar 为自动驾驶汽车和传统汽车提供了行驶的方法。类 AutoCar 作为抽象类 ABCar 的子类,在重写了父类中的抽象方法 run 之外,还额外增加了 auto_run 方法以实现自动驾驶。
from abc import ABCMeta, abstractmethod
class ABCar(metaclass=ABCMeta):
@abstractmethod
def run(self):
pass
class NormalCar(ABCar):
def run(self):
print("汽车正在行驶...")
class AutoCar(ABCar):
def run(self):
print("汽车正在行驶...")
def auto_run(self):
print("汽车正在自动驾驶...")
if __name__ == "__main__":
x = NormalCar()
x.run()
y = AutoCar()
y.run()
y.auto_run()
网友评论