学习SOLID原则,这篇文章就能满足你的需求吗?

2026-05-06 05:161阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2186个文字,预计阅读时间需要9分钟。

学习SOLID原则,这篇文章就能满足你的需求吗?

背景:在我们日常工作中,代码编写写着写着就会出现以下的一些陋习。但是,还好我们有SOLID原则这把尺子,可以拿着它不断地衡量我们写的代码,去除掉代码的陋习。这就是我们要学习SOLID原则的初衷。

背景

在我们日常工作中,代码写着写着就出现下列的一些臭味。但是还好我们有SOLID这把‘尺子’, 可以拿着它不断去衡量我们写的代码,除去代码臭味。这就是我们要学习SOLID原则的原因所在。

设计的臭味
  • 僵化性
    • 具有联动性,动一处,会牵连到其他地方
  • 脆弱性
    • 不敢改动,动一处,全局瘫痪
  • 顽固性
    • 不易改动
  • 粘滞性
    • 耦合性太高
  • 不必要的复杂性
    • 代码设计过于复杂
  • 不必要的重复
    • 提高复用性,减少重复
  • 晦涩性
    • 代码设计不易理解
SRP-单一职责原则
  • 一个类只做一件事情。当然一件事情,不是说类中只有一个方法。而是类中的方法都是属于同一种职责。
  • 不能因为第二职责的原因去改动这个类。

一个很好的例子:在我们封装request库时,我们需要实现以下4个方法.

class MyRequestClient: def post(self): pass def get(self): pass def update(self): pass def delete(self): pass #上面的方法就是属于同一职责。 如何还有其他的方法,那么这个类就不符合单一职责原则。 #例增加以下方法: def get_db_data(self): pass def to_object(self): pass OCP-开放封闭原则

  • 对扩展开放,对修改封闭。
  • 无需改动自身代码,就可以扩展它的行为。
  • 对类的改动往往是新增代码就可以了,而不是去修改原有的代码。
  • 使用子类继承、依赖注入、数据驱动的方法可以实现OCP原则。

首先我们来看一个违反OCP原则的例子。

#bad code def circle_draw(): print(f"this is circle draw") def square_draw(): print(f"this is square draw") def draw_all_shape(shapes): for shape in shapes: if shape == "circle": circle_draw() if shape == "square": square_draw()

这段代码的问题是如果再有新的类型需要draw, 我们需要修改draw_all_shape函数来适配新的类型。

依赖注入实现OCP原则

我们定义了一个抽象类Shape, 子类Square和Circle继承Shape. 并且在子类中重写了父类的方法。函数draw_all_shape是绘制所有图形。

from typing import List from abc import ABCMeta, abstractmethod class Shape(metaclass=ABCMeta): @abstractmethod def draw(self): pass class Square(Shape): def draw(self): print(f"this is square draw") class Circle(Shape): def draw(self): print(f"this is circle draw") def draw_all_shape(shapes: List[Shape]): for shape in shapes: shape.draw()

我们定义了一个抽象类Shape, 子类SquareCircle继承Shape. 并且在子类中重写了父类的方法。函数draw_all_shape是绘制所有图形。

参数注入实现OCP原则

def circle_draw(): print(f"this is circle draw") def square_draw(): print(f"this is square draw") def draw_all_shape_by_function(data: Dict[str,Callable]): for key,value in data.items(): value() data = { "circle": circle_draw, "square": square_draw } draw_all_shape_by_function(data=data) Conclusion

  • 这样的设计的好处是,如果需要再绘制一个三角形,那么我们只需要增加一个新类并继承Shape.无需修改shape类和draw_all_shape就可以实现三角形类的绘制。
  • 当我们在类中或函数中需要使用大量的if-else逻辑判断时,很有可能代码就违反了OCP原则。
LSP:Liskov 替换原则
  • 派生类应该可以替换父类中的方法使用,而不会改变程序原本的功能。
  • 派生类重写方法的参数应该和父类的保持一致或多于父类,不能少于父类。
  • 派生类重写方法的返回值必须和父类返回值类型一致。
  • 违反LSP原则,通常也会违反OCP原则。

首先我们来看一段违法LSP的例子

from typing import Iterable class User(): def __init__(self, user: str) -> None: self.user = user def disable(self) -> None: print(f"{self.user} disable!") class Admin(User): def __init__(self, user: str = "Admin") -> None: self.user = user def disable(self): raise "Admin do not disable!" def delete_user(users: Iterable[User]): for user in users: user.disable()

当执行delete_user时,就会抛出TypeError 错误,Admin类中disable方法违法了LSP替换原则。

Optimize

#Good from typing import Iterable class User(): def __init__(self, user: str) -> None: self.user = user def allow_disable(self): return True def disable(self) -> None: print(f"{self.user} disable!") class Admin(User): def __init__(self, user: str = "Admin") -> None: self.user = user def allow_disable(self): return False def delete_user(users: Iterable[User]) -> None: for user in users: if user.allow_disable: user.disable() Conclusion

  • 上例中通过添加allow_disable 的方法,解决了Admin类不能disable的问题。
  • 当派生类不正确的重写父类方法的时候,就会违反LSP原则,我们在继承类的时候重写方法的时候,尤其- 要注意是否违反了LSP原则。
ISP 接口隔离原则
  • 客户应该不依赖它不使用的方法。
  • 一个类只做一件事。

首先来看一个违反ISP原则的例子:

class Animal(metaclass=ABCMeta): @abstractclassmethod def run(self): pass @abstractclassmethod def speak(self): pass @abstractclassmethod def fly(self): pass class Dog(Animal): def run(self): return "Dog Running" def speak(self): return "Dog Speaking" def fly(self): raise TypeError("Dog can not fly") class Bird(Animal): def run(self): raise TypeError("Bird can not run") def speak(self): return "Bird Speaking" def fly(self): return "Bird fly" def fly_animal(animals: Iterable[Animal]): for animal in animals: animal.fly()

当我们执行fly_animal时,就会抛出TypeError的错误。此时Animal抽象类是一个胖类,违法了ISP原则。

Optimize

  • 将Animal抽象类分解为三个新抽象类,FlyingAnimal, TalkingAnimal, RunningAnimal, 底层代码按需继承。

#good class FlyingAnimal(metaclass=ABCMeta): @abstractclassmethod def fly(self): pass class RunningAnimal(metaclass=ABCMeta): @abstractclassmethod def run(self): pass class TalkingAnimal(metaclass=ABCMeta): @abstractclassmethod def talk(self): pass class Dog(RunningAnimal,TalkingAnimal): def run(self): return "Dog Running" def talk(self): return "Dog Speaking" class Bird(FlyingAnimal, TalkingAnimal): def talk(self): return "Bird Speaking" def fly(self): return "Bird fly" def fly_animal(animals: Iterable[FlyingAnimal]): for animal in animals: print(animal.fly()) Conclusion

  • 接口隔离原则看似和单一职责原则相似,单一职责原则是针对模块,类,方法的设计。接口隔离原则更注重在调用者的角度,按需提供接口。
  • 写更小的类,大多数情况下是个好主意。
  • 违反ISP原则也可能会违反LSP原则和SRP原则。
  • 当子类重写了一个不需要的方法时,很可能违反了ISP原则。
DIP 依赖倒置原则
  • 程序中所有的依赖都应该终止于抽象类或接口。
  • 任何类都不应该从具体类派生。
  • 任何方法都不易应该重写它的任何基类已经实现了的方法。
  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

首先看一个违反DIP原则的例子:

学习SOLID原则,这篇文章就能满足你的需求吗?

class Lamp: def turn_on(self): print("turn on the lamp") def turn_off(self): print("turn off the lamp") class Button(): def __init__(self) -> None: self.lamp = Lamp() def turn_on(self): return self.lamp.turn_on() def turn_off(self): return self.lamp.turn_off()

当有一天,button需要控制televsion时,就需要修改Button类。ButtonLamp 具有强耦合关系。所以,当Lamp变动时,会影响到Button类。违法了DIP原则的高层模块依赖于底层模块。

Optimize

定义一个抽象类ElectricAppliance Button 和 Lamp 都依赖这个抽象类。 解决了ButtonLamp 具有强耦合的问题。

class ElectricAppliance(metaclass=ABCMeta): @abstractmethod def turn_on(self): pass @abstractmethod def turn_off(self): pass class Lamp(ElectricAppliance): def turn_on(self): print("turn on the lamp") def turn_off(self): print("turn off the lamp") class Television(ElectricAppliance): def turn_on(self): print("turn on the televison") def turn_off(self): print("turn off the televison") class Button: def __init__(self, electric_appliance: ElectricAppliance) -> None: self.electric_appliance = electric_appliance def turn_on(self): self.electric_appliance.turn_on() def turn_off(self): self.electric_appliance.turn_off() Conclusion

  • 要确定代码是否违反了DIP原则,需要观察一个类中是否嵌入了调用其他类或函数。如果是,那么很可能是违反了DIP原则。

本文共计2186个文字,预计阅读时间需要9分钟。

学习SOLID原则,这篇文章就能满足你的需求吗?

背景:在我们日常工作中,代码编写写着写着就会出现以下的一些陋习。但是,还好我们有SOLID原则这把尺子,可以拿着它不断地衡量我们写的代码,去除掉代码的陋习。这就是我们要学习SOLID原则的初衷。

背景

在我们日常工作中,代码写着写着就出现下列的一些臭味。但是还好我们有SOLID这把‘尺子’, 可以拿着它不断去衡量我们写的代码,除去代码臭味。这就是我们要学习SOLID原则的原因所在。

设计的臭味
  • 僵化性
    • 具有联动性,动一处,会牵连到其他地方
  • 脆弱性
    • 不敢改动,动一处,全局瘫痪
  • 顽固性
    • 不易改动
  • 粘滞性
    • 耦合性太高
  • 不必要的复杂性
    • 代码设计过于复杂
  • 不必要的重复
    • 提高复用性,减少重复
  • 晦涩性
    • 代码设计不易理解
SRP-单一职责原则
  • 一个类只做一件事情。当然一件事情,不是说类中只有一个方法。而是类中的方法都是属于同一种职责。
  • 不能因为第二职责的原因去改动这个类。

一个很好的例子:在我们封装request库时,我们需要实现以下4个方法.

class MyRequestClient: def post(self): pass def get(self): pass def update(self): pass def delete(self): pass #上面的方法就是属于同一职责。 如何还有其他的方法,那么这个类就不符合单一职责原则。 #例增加以下方法: def get_db_data(self): pass def to_object(self): pass OCP-开放封闭原则

  • 对扩展开放,对修改封闭。
  • 无需改动自身代码,就可以扩展它的行为。
  • 对类的改动往往是新增代码就可以了,而不是去修改原有的代码。
  • 使用子类继承、依赖注入、数据驱动的方法可以实现OCP原则。

首先我们来看一个违反OCP原则的例子。

#bad code def circle_draw(): print(f"this is circle draw") def square_draw(): print(f"this is square draw") def draw_all_shape(shapes): for shape in shapes: if shape == "circle": circle_draw() if shape == "square": square_draw()

这段代码的问题是如果再有新的类型需要draw, 我们需要修改draw_all_shape函数来适配新的类型。

依赖注入实现OCP原则

我们定义了一个抽象类Shape, 子类Square和Circle继承Shape. 并且在子类中重写了父类的方法。函数draw_all_shape是绘制所有图形。

from typing import List from abc import ABCMeta, abstractmethod class Shape(metaclass=ABCMeta): @abstractmethod def draw(self): pass class Square(Shape): def draw(self): print(f"this is square draw") class Circle(Shape): def draw(self): print(f"this is circle draw") def draw_all_shape(shapes: List[Shape]): for shape in shapes: shape.draw()

我们定义了一个抽象类Shape, 子类SquareCircle继承Shape. 并且在子类中重写了父类的方法。函数draw_all_shape是绘制所有图形。

参数注入实现OCP原则

def circle_draw(): print(f"this is circle draw") def square_draw(): print(f"this is square draw") def draw_all_shape_by_function(data: Dict[str,Callable]): for key,value in data.items(): value() data = { "circle": circle_draw, "square": square_draw } draw_all_shape_by_function(data=data) Conclusion

  • 这样的设计的好处是,如果需要再绘制一个三角形,那么我们只需要增加一个新类并继承Shape.无需修改shape类和draw_all_shape就可以实现三角形类的绘制。
  • 当我们在类中或函数中需要使用大量的if-else逻辑判断时,很有可能代码就违反了OCP原则。
LSP:Liskov 替换原则
  • 派生类应该可以替换父类中的方法使用,而不会改变程序原本的功能。
  • 派生类重写方法的参数应该和父类的保持一致或多于父类,不能少于父类。
  • 派生类重写方法的返回值必须和父类返回值类型一致。
  • 违反LSP原则,通常也会违反OCP原则。

首先我们来看一段违法LSP的例子

from typing import Iterable class User(): def __init__(self, user: str) -> None: self.user = user def disable(self) -> None: print(f"{self.user} disable!") class Admin(User): def __init__(self, user: str = "Admin") -> None: self.user = user def disable(self): raise "Admin do not disable!" def delete_user(users: Iterable[User]): for user in users: user.disable()

当执行delete_user时,就会抛出TypeError 错误,Admin类中disable方法违法了LSP替换原则。

Optimize

#Good from typing import Iterable class User(): def __init__(self, user: str) -> None: self.user = user def allow_disable(self): return True def disable(self) -> None: print(f"{self.user} disable!") class Admin(User): def __init__(self, user: str = "Admin") -> None: self.user = user def allow_disable(self): return False def delete_user(users: Iterable[User]) -> None: for user in users: if user.allow_disable: user.disable() Conclusion

  • 上例中通过添加allow_disable 的方法,解决了Admin类不能disable的问题。
  • 当派生类不正确的重写父类方法的时候,就会违反LSP原则,我们在继承类的时候重写方法的时候,尤其- 要注意是否违反了LSP原则。
ISP 接口隔离原则
  • 客户应该不依赖它不使用的方法。
  • 一个类只做一件事。

首先来看一个违反ISP原则的例子:

class Animal(metaclass=ABCMeta): @abstractclassmethod def run(self): pass @abstractclassmethod def speak(self): pass @abstractclassmethod def fly(self): pass class Dog(Animal): def run(self): return "Dog Running" def speak(self): return "Dog Speaking" def fly(self): raise TypeError("Dog can not fly") class Bird(Animal): def run(self): raise TypeError("Bird can not run") def speak(self): return "Bird Speaking" def fly(self): return "Bird fly" def fly_animal(animals: Iterable[Animal]): for animal in animals: animal.fly()

当我们执行fly_animal时,就会抛出TypeError的错误。此时Animal抽象类是一个胖类,违法了ISP原则。

Optimize

  • 将Animal抽象类分解为三个新抽象类,FlyingAnimal, TalkingAnimal, RunningAnimal, 底层代码按需继承。

#good class FlyingAnimal(metaclass=ABCMeta): @abstractclassmethod def fly(self): pass class RunningAnimal(metaclass=ABCMeta): @abstractclassmethod def run(self): pass class TalkingAnimal(metaclass=ABCMeta): @abstractclassmethod def talk(self): pass class Dog(RunningAnimal,TalkingAnimal): def run(self): return "Dog Running" def talk(self): return "Dog Speaking" class Bird(FlyingAnimal, TalkingAnimal): def talk(self): return "Bird Speaking" def fly(self): return "Bird fly" def fly_animal(animals: Iterable[FlyingAnimal]): for animal in animals: print(animal.fly()) Conclusion

  • 接口隔离原则看似和单一职责原则相似,单一职责原则是针对模块,类,方法的设计。接口隔离原则更注重在调用者的角度,按需提供接口。
  • 写更小的类,大多数情况下是个好主意。
  • 违反ISP原则也可能会违反LSP原则和SRP原则。
  • 当子类重写了一个不需要的方法时,很可能违反了ISP原则。
DIP 依赖倒置原则
  • 程序中所有的依赖都应该终止于抽象类或接口。
  • 任何类都不应该从具体类派生。
  • 任何方法都不易应该重写它的任何基类已经实现了的方法。
  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

首先看一个违反DIP原则的例子:

学习SOLID原则,这篇文章就能满足你的需求吗?

class Lamp: def turn_on(self): print("turn on the lamp") def turn_off(self): print("turn off the lamp") class Button(): def __init__(self) -> None: self.lamp = Lamp() def turn_on(self): return self.lamp.turn_on() def turn_off(self): return self.lamp.turn_off()

当有一天,button需要控制televsion时,就需要修改Button类。ButtonLamp 具有强耦合关系。所以,当Lamp变动时,会影响到Button类。违法了DIP原则的高层模块依赖于底层模块。

Optimize

定义一个抽象类ElectricAppliance Button 和 Lamp 都依赖这个抽象类。 解决了ButtonLamp 具有强耦合的问题。

class ElectricAppliance(metaclass=ABCMeta): @abstractmethod def turn_on(self): pass @abstractmethod def turn_off(self): pass class Lamp(ElectricAppliance): def turn_on(self): print("turn on the lamp") def turn_off(self): print("turn off the lamp") class Television(ElectricAppliance): def turn_on(self): print("turn on the televison") def turn_off(self): print("turn off the televison") class Button: def __init__(self, electric_appliance: ElectricAppliance) -> None: self.electric_appliance = electric_appliance def turn_on(self): self.electric_appliance.turn_on() def turn_off(self): self.electric_appliance.turn_off() Conclusion

  • 要确定代码是否违反了DIP原则,需要观察一个类中是否嵌入了调用其他类或函数。如果是,那么很可能是违反了DIP原则。