掌握“里氏代换原则”,写出稳定可靠的代码!

作者:棋牌游戏开发公司阅读:发布时间:2023-12-21 14:00

摘要:面向对象程序设计(Object-Oriented Programming, OOP)是软件开发中常用的一种编程思想。在OOP中,封装、继承、多态为主要特性...

 

面向对象程序设计(Object-Oriented Programming, OOP)是软件开发中常用的一种编程思想。在OOP中,封装、继承、多态为主要特性,而继承是更加通用、也是更加基础的特性。在继承中,里氏代换原则(Liskov Substitution Principle, LSP)是非常重要的一项原则,它可使得代码更加稳定可靠

掌握“里氏代换原则”,写出稳定可靠的代码!

LSP是由计算机专家芭芭拉·利斯科夫(Barbara Liskov)提出的。大致定义如下:如果S是T的子类,那么任何类型为T的对象在程序中可以被替换为类型为S的对象,而不会影响程序的正确性。也就是说,子类对象必须能够替换父类对象并且以父类对象的行为来使用,这就需要子类去保证实现了父类的所有行为,不改变已有的父类方法的前置条件、后置条件和异常返回值。

LSP的定义虽然简单,但是它所关注的方面却深刻而广泛。在应用LSP时,从编写代码到软件系统设计,甚至到软件开发管理等各个方面都要受到它的考验。下面具体介绍LSP原则对代码编写的影响。

1.避免子类破坏父类的行为

LSP的核心是确保子类能够替换父类,这就要求子类必须保证不会改变已有的父类方法的前置条件,后置条件和异常返回值。假设A是B的子类,B中有一个方法foo,那么A中也可以有foo方法,并且A的foo方法接收的参数类型和返回类型必须和B中的foo方法相同。在这个基础上,A可以对foo方法做些自己的实现。

然而,如果A对foo方法的实现方式在某些情况下和B的实现不一样,那么A就不能完全取代B。这会导致A不能替换B,也违反了LSP。在实现子类时,必须保证子类的行为不会破坏父类。如果子类需要有自己不同的行为,那么我们可以增加父类方法的参数或者返回值,以便子类可以实现自己的行为。

接下来,我们将用代码来说明,假设我们有如下的抽象汽车类AbstractCar。

```python

class AbstractCar:

def run(self):

raise NotImplementedError

```

假设我们有一种新型的汽车class NewCar(AbstractCar):,它增加了一些自己的行为。我们不希望NewCar破坏父类AbstractCar的方法,因此我们可以通过扩展接口的方式来添加新的行为。

```python

class AbstractCar:

def run(self):

raise NotImplementedError

def door_close(self):

raise NotImplementedError

def door_open(self):

raise NotImplementedError

def turn_left(self):

raise NotImplementedError

def turn_right(self):

raise NotImplementedError

class NewCar(AbstractCar):

def run(self):

print("NewCar run")

def door_close(self):

print("NewCar closed the door")

def door_open(self):

print("NewCar opened the door")

def turn_left(self):

print("NewCar turned left")

def turn_right(self):

print("NewCar turned right")

def alarm(self):

print("NewCar alarm")

```

2.子类可以增加方法

LSP要求子类不可破坏父类的行为,但是不要求子类不能增加自己的行为。对于抽象的父类,具体的子类可以在不影响原有父类方法的情况下,增加自己的新方法。这是因为父类和子类之间,不仅仅可以通过继承来实现代码重用,同时,通过组合也可以实现代码的复用。

接下来我们将通过代码来演示,增加一个MonkeyCar子类,并且增加一个猴子的方法Monkey :

```python

class MonkeyCar(AbstractCar):

def run(self):

print("MonkeyCar run")

def door_close(self):

print("MonkeyCar closed the door")

def door_open(self):

print("MonkeyCar opened the door")

def turn_left(self):

print("MonkeyCar turned left")

def turn_right(self):

print("MonkeyCar turned right")

def monkey(self):

print("Monkey come out")

```

3.使用interface定接口规范

LSP强调的是接口规范的一致性。设想以下场景:如果我们有两个汽车类,他们都是从AbstractCar类继承的,并且都有run方法。那么对于上层代码(主程序等)来说,它们是一样的,程序不需要关心具体是哪个类。这就意味着代码的耦合性降低了。但是如果新添加一个普通车CommonCar类,它也是从AbstractCar类继承的,但是却没有door_open方法,这会使得上层代码在处理普通车的时候需要加入特判,这本质上是不符合开闭原则的。因此,在OOP中,不仅可以通过继承来实现代码的扩展,同时,在引入新的类时,我们可以通过接口来规范方法的实现、命名以及其他要求。下面是具体的实现方式:

我们定义一个新的接口,来规范每一个汽车都必须具备run和run_speed两个方法

```python

from abc import ABCMeta, abstractmethod

class ICar(metaclass=ABCMeta):

掌握“里氏代换原则”,写出稳定可靠的代码!

@abstractmethod

def run(self):

pass

@abstractmethod

def run_speed(self, speed):

pass

```

在我们不同的汽车类中,通过继承来实现不同方式的run方法和run_speed方法。

新车的实现:

```python

class NewCar(ICar):

def run(self):

print("NewCar run")

def run_speed(self, speed):

print(f"NewCar running at {speed} speed.")

```

猴子车的实现:

```python

class MonkeyCar(ICar):

def run(self):

print("MonkeyCar run")

def run_speed(self, speed):

print(f"MonkeyCar running {speed} speed.")

def door_close(self):

print("MonkeyCar closed the door")

def door_open(self):

print("MonkeyCar opened the door")

def turn_left(self):

print("MonkeyCar turned left")

def turn_right(self):

print("MonkeyCar turned right")

def monkey(self):

print("Monkey come out")

```

这时,我们添加一个普通车的实现,让它也遵循我们ICar接口实现即可。

4.通过泛型来应用LSP

在OOP中,泛型是一种强大的方式,可以在编写代码时,避免很多冗余的类型检查操作。Python 3.x对泛型添加了很好的支持,可以很方便的应用LSP。我们在ICar中引入TypeVar,类似于Java中的,然后我们就可以通过TypeVar来实现泛型中的范型参数。

```python

from abc import ABCMeta, abstractmethod

from typing import TypeVar

T = TypeVar("T", bound="ICar")

class ICar(metaclass=ABCMeta):

@abstractmethod

def run(self) -> None:

pass

@abstractmethod

def run_speed(self, speed: float) -> None:

pass

@abstractmethod

def same(self, x: T) -> bool:

pass

```

代码实现:

```python

class CommonCar(ICar):

def run(self):

print("CommonCar run")

def run_speed(self, speed):

print(f"CommonCar running {speed} speed.")

def same(self, x: T) -> bool:

return isinstance(x, CommonCar)

```

这其中最重要的是我们定义了一个泛型T去继承ICar,并且实现了一个same的方法,判断变量的类型参数是否匹配。这样,在应用泛型参数时,我们就可以很容易的应用LSP原则实现代码的重用。

总结:

继承是OOP中的重要特性,也是代码复用的核心。同时,代码的稳定可靠性是在开发过程中必须保证的一个重要性质。LSP原则作为一个重要的编码规约,帮助开发人员在编写代码时遵循良好的设计原则,有效地提高了代码稳定可靠性。在遵循LSP原则的基础上,我们可以更加高效地编写出复用性更好,扩展性更强的代码。

  • 本文链接:https://fysfzk.com/hyzx/2875.html

  • 本文由 棋牌游戏开发公司小编,整理排版发布,转载请注明出处。部分文章图片来源于网络,如有侵权,请与先发网络联系删除。
  • 相关推荐

    微信二维码

    clwl6868

    长按复制微信号,添加好友

    微信联系

    在线咨询

    点击这里给我发消息QQ客服专员


    点击这里给我发消息电话客服专员


    在线咨询

    免费通话


    24h咨询☎️:132-5572-7217


    🔺🔺 棋牌游戏开发24H咨询电话 🔺🔺

    免费通话
    返回顶部