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)

  • 定义:高层模块不应该依赖低层模块,两者都应该依赖于抽象;抽象不应依赖于细节,细节应依赖于抽象。
  • 目的:减少类之间的直接依赖,通过依赖抽象而非具体实现来增强系统的可维护性和灵活性。