SOLID 原则是一组(五个)软件设计原则,用于指导软件开发人员设计和实现高质量的、易于维护和扩展的软件。它是由罗伯特· C ·马丁在其著作《Agile Software Development, Principles, Patterns, and Practices》中提出的,是目前软件工程界被广泛接受的一种软件设计理念。
单一职责原则(Single Responsibility Principle,SRP)
一个类应该只有一个引起它变化的原因,这意味着一个类应该只负责一件事情。
class ShoppingCart:
def __init__(self):
self.items = []
self.total = 0
def add_item(self, item):
self.items.append(item)
self.total += item.price
def remove_item(self, item):
self.items.remove(item)
self.total -= item.price
def print_receipt(self):
print('Items:')
for item in self.items:
print(f'{item.name} - ${item.price}')
print(f'Total: ${self.total}')
例子中的 ShoppingCart
类同时负责处理购物车相关的任务和输出相关的任务,它的 print_receipt()
方法应该被拆分为一个独立的类别或方法,以实现单一职责原则。
开放封闭原则(Open-Closed Principle,OCP)
开闭原则指软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着软件应该设计成可以在不修改现有代码的情况下进行扩展,从而增加新功能。
假设我们有一个计算不同形状面积的程序:
class AreaCalculator:
def calculate_area(self, shape):
if isinstance(shape, Rectangle):
return shape.width * shape.height
elif isinstance(shape, Circle):
return 3.14 * shape.radius * shape.radius
else:
raise TypeError("Unknown shape!")
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class Circle:
def __init__(self, radius):
self.radius = radius
上述代码的问题在于,如果我们要添加一个新的形状,比如 Triangle
,我们需要修改 AreaCalculator
类中的 calculate_area
方法,这违反了开闭原则。
正确的做法是使用多态来实现对扩展开放的设计,使得添加新功能时只需要增加新的实现类即可,不需要修改现有实体:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def calculate_area(self):
return 0.5 * self.base * self.height
class AreaCalculator:
def calculate_area(self, shape: Shape):
return shape.calculate_area()
里氏替换原则(Liskov Substitution Principle,LSP)
- 定义:子类型必须能够替换掉它们的父类型。
- 目的:增强程序的可维护性,通过保证子类可以替换父类而不影响程序的正确性,保持继承体系的一致性。
接口隔离原则(Interface Segregation Principle,ISP)
客户端不应该被迫依赖于它不使用的接口,接口应该被拆分为更小和更具体的部分,以便客户端只需要知道它们所需的部分。
下面是一段违反 ISP 的代码:
class Machine:
def print(self, document):
pass
def fax(self, document):
pass
def scan(self, document):
pass
class MultiFunctionPrinter(Machine):
def print(self, document):
print("Printing")
def fax(self, document):
print("Faxing")
def scan(self, document):
print("Scanning")
示例中的 Machine 是一个代表机器的接口,包含了 print、fax 和 scan 三个方法。 MultiFunctionPrinter 是一个多功能打印机,必须实现的只有 print 方法,fax 和 scan 方法取决于打印机的种类,不是所有打印机都需要实现。但由于 Machine 接口中包含了所有方法,MultiFunctionPrinter 被迫实现它可能不需要的 fax 和 scan 方法。
符合 ISP 的设计:
class Printer:
def print(self, document):
pass
class Fax:
def fax(self, document):
pass
class Scanner:
def scan(self, document):
pass
class MultiFunctionDevice(Printer, Fax, Scanner):
def print(self, document):
print("Printing")
def fax(self, document):
print("Faxing")
def scan(self, document):
print("Scanning")
将 Machine 接口一分为三,打印机可以根据功能实现所需要的接口。
依赖倒置原则(Dependency Inversion Principle,DIP)
- 定义:高层模块不应该依赖低层模块,两者都应该依赖于抽象;抽象不应依赖于细节,细节应依赖于抽象。
- 目的:减少类之间的直接依赖,通过依赖抽象而非具体实现来增强系统的可维护性和灵活性。