程序员圈子里流传着一句老话,代码写得出来不难,难的是半年后自己还看得懂。这句玩笑背后藏着软件工程最核心的痛点------如何让代码在需求不断变化的现实世界里,依然保持清晰、可维护、不崩溃。
SOLID 原则就是为了应对这个痛点而生的。它由 Robert C. Martin(业内人称 Uncle Bob)在 2000 年前后系统整理提出,五个字母分别对应五条面向对象设计的黄金法则 。这套东西流传了二十多年依然是各大公司代码评审的标准,不是因为它多高深,而是因为它踩过的坑实在太真实了。
下面就把这五条原则掰开揉碎,逐一用 Python 代码演示给你看,顺便聊聊 Python 世界里那些同样重要、却常被忽略的编程哲学。
🧭 SOLID 到底是哪五个字母
先给个全景图,心里有个谱再往下看会轻松很多。
| 字母 | 全称 | 一句话理解 |
|---|---|---|
| S | Single Responsibility Principle(单一职责原则) | 一个类只干一件事 |
| O | Open/Closed Principle(开闭原则) | 加新功能靠扩展,不靠改老代码 |
| L | Liskov Substitution Principle(里氏替换原则) | 子类要能无缝顶替父类 |
| I | Interface Segregation Principle(接口隔离原则) | 别强迫别人实现用不到的方法 |
| D | Dependency Inversion Principle(依赖倒置原则) | 依赖抽象,别依赖具体实现 |
这五条原则并不是孤立存在的,它们之间互相呼应,共同指向一个目标------降低耦合,提高代码的可扩展性和可测试性 。
🔍 逐条拆解,配上真实代码
1. 单一职责原则(SRP)
核心思想很朴素,一个类应该只有一个引起它变化的原因。听起来抽象,落地到代码就是别让一个类身兼数职。
反面教材长这样,一个 Employee 类既管员工信息,又管薪资计算,还顺带管数据库存储:
python
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def calculate_pay(self):
return self.salary * 1.1 # 加薪逻辑
def save_to_database(self):
# 直接把数据库操作也塞进来
print(f"Saving {self.name} to DB...")
问题在于,一旦薪资计算规则变了,或者数据库换了个存储方案,都要来改这一个类,风险叠加,谁改谁提心吊胆。拆开就清爽多了:
python
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class PayCalculator:
def calculate_pay(self, employee):
return employee.salary * 1.1
class EmployeeRepository:
def save(self, employee):
print(f"Saving {employee.name} to DB...")
每个类只对一类变化负责,改薪资算法不会碰到存储逻辑,改存储方式也不会牵连薪资计算 。
2. 开闭原则(OCP)
这条原则说的是,软件实体应该对扩展开放,对修改关闭。翻译成人话就是,新增需求时应该靠加代码 而不是改代码。
假设你在写一个图形面积计算器,一开始只有矩形:
python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class AreaCalculator:
def calculate(self, shape):
if isinstance(shape, Rectangle):
return shape.width * shape.height
后来产品经理说要加圆形。你的第一反应可能是在 calculate 里加个 elif,但这样每加一种图形都要回头改这个方法,改一次就有一次出 bug 的风险。更好的做法是让每种图形自己知道怎么算面积:
python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class AreaCalculator:
def calculate(self, shape: Shape):
return shape.area()
以后不管加三角形还是加菱形,AreaCalculator 这个类一行都不用改 ,直接新写一个类继承 Shape 就行 。
3. 里氏替换原则(LSP)
这条最容易被误解,也最容易被写错。它要求的是,任何父类能出现的地方,子类都必须能无缝替换,不能出现行为异常。
用更严谨一点的说法,子类的前置条件不能比父类更严格,后置条件不能比父类更宽松,写成公式大概是这个意思:
P子类≤P父类,Q子类≥Q父类
经典的翻车案例是正方形继承矩形。数学上正方形是特殊的矩形没错,但代码里这么继承会出大问题:
python
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def set_width(self, width):
self.width = width
def set_height(self, height):
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def set_width(self, width):
self.width = width
self.height = width # 强行同步,破坏了矩形的语义
def set_height(self, height):
self.width = height
self.height = height
如果有一段代码假设 Rectangle 的宽高可以独立设置,一旦传进来的是 Square,行为就悄悄变了,这种隐藏 bug 排查起来能让人抓狂。正确做法是不要强行建立继承关系,让两者各自独立实现共同的接口:
python
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
这样两个类各自独立,谁也不会因为对方的语义假设而莫名其妙出错 。
4. 接口隔离原则(ISP)
这条讲的是,不要设计臃肿的大接口,把很多互不相关的方法硬塞在一起,逼着实现类去写一堆用不到的空方法。
想象一个 Worker 接口,既要求实现 work() 又要求实现 eat():
python
from abc import ABC, abstractmethod
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
class RobotWorker(Worker):
def work(self):
print("机器人在工作")
def eat(self):
raise NotImplementedError("机器人不需要吃饭") # 尴尬了
机器人根本不吃饭,却被迫实现一个毫无意义的方法。拆分成两个小接口就干净多了:
python
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class HumanWorker(Workable, Eatable):
def work(self):
print("人类在工作")
def eat(self):
print("人类在吃饭")
class RobotWorker(Workable):
def work(self):
print("机器人在工作")
接口要小而专一,谁需要什么就实现什么,不该被无关的方法拖累。
5. 依赖倒置原则(DIP)
这是五条里最能提升架构质量的一条,简单说就是高层模块不应该依赖低层模块,两者都应该依赖抽象。
反面例子,一个 NotificationService 直接写死依赖某个具体的邮件发送类:
python
class EmailSender:
def send(self, message):
print(f"发送邮件: {message}")
class NotificationService:
def __init__(self):
self.sender = EmailSender() # 写死了具体实现
def notify(self, message):
self.sender.send(message)
想加个短信通知,只能去改 NotificationService 的内部代码,耦合得死死的。改成依赖抽象接口,就灵活多了:
python
class MessageSender(ABC):
@abstractmethod
def send(self, message):
pass
class EmailSender(MessageSender):
def send(self, message):
print(f"发送邮件: {message}")
class SMSSender(MessageSender):
def send(self, message):
print(f"发送短信: {message}")
class NotificationService:
def __init__(self, sender: MessageSender): # 依赖抽象,而非具体类
self.sender = sender
def notify(self, message):
self.sender.send(message)
# 使用时想换渠道,直接换个实现类传进去就行
service = NotificationService(SMSSender())
service.notify("你的验证码是123456")
这种写法在 Python 里还有个专属名字叫依赖注入,测试的时候可以轻松传入一个假的 Mock 对象,不用真的发邮件发短信,测试效率直接起飞 。
📊 用一张图理清五者的关系
SOLID 五条原则并不是各自为战,它们环环相扣,最终都服务于同一个目标------让代码在变化面前依然稳如老狗。

可以看到,SRP 管的是类内部的职责边界,OCP 管的是扩展方式,LSP 管的是继承体系的行为正确性,ISP 管的是接口的粒度,DIP 管的是模块间的依赖方向。五条拧成一股绳,最终指向低耦合、高内聚这个大目标。
💡 Python 里还有哪些同样重要的编程原则
SOLID 是面向对象设计的原则,但 Python 语言本身还有一套自己的哲学,甚至可以说这套哲学比 SOLID 更早刻进了 Python 程序员的骨子里。
DRY(Don't Repeat Yourself)
别重复造轮子,同一段逻辑不要在代码里到处复制粘贴。一旦需求变了,改一处漏改十处,简直是自找麻烦。
python
# 反面: 重复计算折扣逻辑
def calculate_price_a(price):
return price * 0.9
def calculate_price_b(price):
return price * 0.9
# 正面: 抽成一个函数复用
def apply_discount(price, rate=0.9):
return price * rate
KISS(Keep It Simple, Stupid)
能用简单方案解决的问题,别炫技上复杂设计模式。Python 社区特别推崇这一点,代码追求的是可读性优先,而不是秀技巧。
python
# 反面: 过度设计一个简单的求和
class SumCalculatorFactory:
def create_calculator(self):
return SumCalculator()
class SumCalculator:
def calculate(self, numbers):
return sum(numbers)
# 正面: 一行搞定
total = sum(numbers)
YAGNI(You Aren't Gonna Need It)
别为了想象中可能永远不会发生的需求,提前写一堆用不上的扩展接口。这条和 KISS 是一对好搭档,都在提醒你别过度设计。
PEP 8 与 Zen of Python
Python 官方风格指南 PEP 8 规定了命名规范、缩进、行长度这些细节,让整个社区的代码风格高度统一 。而 Python 之禅(在解释器里敲 import this 就能看到)用一句句诗意的话总结了 Python 的设计哲学,比如明确胜于隐晦 、简单胜于复杂 、可读性很重要。
Composition over Inheritance(组合优于继承)
这条其实和 LSP 相辅相成。继承容易导致层层嵌套的类体系,一旦父类改动,子类可能莫名其妙出问题。组合的做法是把功能拆成独立的小组件,通过持有对象 而不是继承对象来复用逻辑。
python
# 继承方式容易导致体系僵化
class FlyingCar(Car, Airplane):
pass
# 组合方式更灵活
class Car:
def __init__(self, engine, wheels):
self.engine = engine
self.wheels = wheels
def drive(self):
self.engine.start()
🧷 写在最后
SOLID 原则和 DRY、KISS、YAGNI 这些理念说到底都在回答同一个问题------代码是写给人看的,顺便让机器执行。刚开始写代码的时候,可能会觉得这些原则太多讲究、太啰嗦,但等到项目跑到几万行代码、几十个人协作维护的时候,你就会真切体会到,当初那份克制和规范,救了自己一命。
Python 这门语言本身骨子里就崇尚简洁和优雅,把 SOLID 的设计智慧和 Python 的哲学结合起来用,写出来的代码往往既好读又耐用。与其说这是一堆规则,不如说这是一群踩过无数坑的前辈,替你把弯路提前标好了。
参考资料
Wikipedia. SOLID . en.wikipedia.org/wiki/SOLID
Splunk. SOLID Design Principles: Hands-On Examples . www.splunk.com/en_us/blog/...
DigitalOcean. SOLID: The First Five Principles of Object-Oriented Design . www.digitalocean.com/community/c...
Real Python. SOLID Principles: Improve Object-Oriented Design in Python . realpython.com/solid-princ...
GeeksforGeeks. SOLID Principles with Real Life Examples . www.geeksforgeeks.org/system-desi...