Python SOLID原则实践
SOLID是面向对象设计的五大原则,是编写可维护代码的基础。
单一职责原则(SRP)
原则定义
一个类应该只有一个引起它变化的原因。
违反SRP示例
Python
# 违反SRP:一个类承担多种职责
class UserManager:
def __init__(self, db):
self.db = db
# 用户数据管理职责
def create_user(self, name, email):
user = User(name, email)
self.db.save(user)
def get_user(self, user_id):
return self.db.query(User).get(user_id)
# 用户验证职责
def validate_email(self, email):
return '@' in email
def validate_name(self, name):
return len(name) > 0
# 邮件发送职责
def send_welcome_email(self, user):
email_body = f"Welcome {user.name}!"
self.email_client.send(user.email, email_body)
# 日志记录职责
def log_user_creation(self, user):
self.logger.info(f"User created: {user.id}")
# 报告生成职责
def generate_user_report(self, user_id):
user = self.get_user(user_id)
return self.report_generator.create_report(user)
符合SRP重构
Python
# 每个类只有一个职责
# 用户数据管理
class UserRepository:
def __init__(self, db):
self.db = db
def save(self, user):
self.db.add(user)
def find(self, user_id):
return self.db.query(User).get(user_id)
# 用户验证
class UserValidator:
def validate(self, name, email):
errors = []
if not self._validate_name(name):
errors.append("Invalid name")
if not self._validate_email(email):
errors.append("Invalid email")
return errors
def _validate_name(self, name):
return len(name) > 0
def _validate_email(self, email):
return '@' in email
# 邮件服务
class EmailService:
def __init__(self, email_client):
self.client = email_client
def send_welcome(self, user):
body = f"Welcome {user.name}!"
self.client.send(user.email, body)
# 用户服务(协调者)
class UserService:
def __init__(self, repository, validator, email_service):
self.repo = repository
self.validator = validator
self.email = email_service
def create_user(self, name, email):
errors = self.validator.validate(name, email)
if errors:
raise ValueError(errors)
user = User(name, email)
self.repo.save(user)
self.email.send_welcome(user)
return user
开放封闭原则(OCP)
原则定义
软件实体应该对扩展开放,对修改关闭。
违反OCP示例
Python
# 违反OCP:添加新类型需要修改现有代码
class PaymentProcessor:
def process(self, payment_type, amount):
if payment_type == 'credit_card':
return self._process_credit_card(amount)
elif payment_type == 'paypal':
return self._process_paypal(amount)
elif payment_type == 'bank_transfer':
return self._process_bank_transfer(amount)
# 添加新支付方式需要修改此方法
def _process_credit_card(self, amount):
pass
def _process_paypal(self, amount):
pass
def _process_bank_transfer(self, amount):
pass
符合OCP重构
Python
# 使用策略模式,添加新支付方式不修改现有代码
from abc import ABC, abstractmethod
# 抽象支付策略
class PaymentStrategy(ABC):
@abstractmethod
def process(self, amount):
pass
# 具体策略实现
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number, cvv):
self.card_number = card_number
self.cvv = cvv
def process(self, amount):
# 信用卡处理逻辑
return {'status': 'success', 'method': 'credit_card'}
class PayPalPayment(PaymentStrategy):
def __init__(self, email):
self.email = email
def process(self, amount):
# PayPal处理逻辑
return {'status': 'success', 'method': 'paypal'}
class BankTransferPayment(PaymentStrategy):
def process(self, amount):
return {'status': 'success', 'method': 'bank_transfer'}
# 新增支付方式,只需添加新类,无需修改PaymentProcessor
class AlipayPayment(PaymentStrategy):
def process(self, amount):
return {'status': 'success', 'method': 'alipay'}
# 处理器
class PaymentProcessor:
def process(self, strategy: PaymentStrategy, amount):
return strategy.process(amount)
# 使用
processor = PaymentProcessor()
result = processor.process(CreditCardPayment('1234', '123'), 100)
里氏替换原则(LSP)
原则定义
子类必须能够替换其基类而不影响程序正确性。
违反LSP示例
Python
# 违反LSP:子类行为与基类不一致
class Bird:
def fly(self):
return "Flying"
class Sparrow(Bird):
def fly(self):
return "Sparrow flying"
class Penguin(Bird):
def fly(self):
raise Exception("Penguins can't fly!") # 违反LSP
def make_bird_fly(bird: Bird):
bird.fly() # Penguin会抛异常
make_bird_fly(Penguin()) # 出错
符合LSP重构
Python
# 重新设计继承层次
class Bird:
def move(self):
return "Moving"
class FlyingBird(Bird):
def fly(self):
return "Flying"
class SwimmingBird(Bird):
def swim(self):
return "Swimming"
class Sparrow(FlyingBird):
def fly(self):
return "Sparrow flying"
class Penguin(SwimmingBird):
def swim(self):
return "Penguin swimming"
def make_bird_move(bird: Bird):
bird.move() # 所有鸟类都能移动
make_bird_move(Penguin()) # 正常工作
接口隔离原则(ISP)
原则定义
客户端不应该依赖它不使用的接口。
违反ISP示例
Python
# 违反ISP:接口过于庞大
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
@abstractmethod
def sleep(self):
pass
class HumanWorker(Worker):
def work(self):
print("Working")
def eat(self):
print("Eating")
def sleep(self):
print("Sleeping")
class RobotWorker(Worker):
def work(self):
print("Working")
def eat(self):
raise Exception("Robot doesn't eat") # 不需要但必须实现
def sleep(self):
raise Exception("Robot doesn't sleep") # 不需要但必须实现
符合ISP重构
Python
# 接口分离,各取所需
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Feedable(ABC):
@abstractmethod
def eat(self):
pass
class Sleepable(ABC):
@abstractmethod
def sleep(self):
pass
class HumanWorker(Workable, Feedable, Sleepable):
def work(self):
print("Working")
def eat(self):
print("Eating")
def sleep(self):
print("Sleeping")
class RobotWorker(Workable): # 只实现需要的接口
def work(self):
print("Working")
# 机器人不需要eat和sleep接口
依赖反转原则(DIP)
原则定义
高层模块不应依赖低层模块,两者都应依赖抽象。
违反DIP示例
Python
# 违反DIP:高层模块直接依赖低层模块
class UserService:
def __init__(self):
# 直接依赖具体实现
self.db = MySQLDatabase()
self.logger = FileLogger()
self.email = SMTPClient()
def create_user(self, name, email):
user = User(name, email)
self.db.save(user)
self.logger.log(f"Created user {name}")
self.email.send(email, "Welcome!")
# 切换数据库或日志系统需要修改UserService
符合DIP重构
Python
# 高层模块依赖抽象接口
from abc import ABC, abstractmethod
# 抽象接口
class Database(ABC):
@abstractmethod
def save(self, entity):
pass
@abstractmethod
def find(self, entity_id):
pass
class Logger(ABC):
@abstractmethod
def log(self, message):
pass
class EmailSender(ABC):
@abstractmethod
def send(self, to, message):
pass
# 具体实现
class MySQLDatabase(Database):
def save(self, entity):
pass
def find(self, entity_id):
pass
class PostgreSQLDatabase(Database):
def save(self, entity):
pass
def find(self, entity_id):
pass
class FileLogger(Logger):
def log(self, message):
pass
# 高层模块依赖抽象
class UserService:
def __init__(self, db: Database, logger: Logger, email: EmailSender):
self.db = db # 依赖抽象,可替换任何实现
self.logger = logger
self.email = email
def create_user(self, name, email):
user = User(name, email)
self.db.save(user)
self.logger.log(f"Created user {name}")
self.email.send(email, "Welcome!")
# 依赖注入
service = UserService(
PostgreSQLDatabase(),
FileLogger(),
SMTPClient()
)
SOLID原则综合应用
完整示例
Python
from abc import ABC, abstractmethod
from typing import List
# 抽象层(DIP)
class NotificationChannel(ABC):
@abstractmethod
def send(self, recipient: str, message: str) -> bool:
pass
class NotificationTemplate(ABC):
@abstractmethod
def render(self, data: dict) -> str:
pass
# 具体实现(OCP扩展)
class EmailChannel(NotificationChannel):
def __init__(self, smtp_client):
self.client = smtp_client
def send(self, recipient, message):
return self.client.send_email(recipient, message)
class SMSChannel(NotificationChannel):
def send(self, recipient, message):
return self.sms_client.send_sms(recipient, message)
class WelcomeTemplate(NotificationTemplate):
def render(self, data):
return f"Welcome {data['name']}!"
# 单一职责(SRP)
class NotificationSender:
def __init__(self, channel: NotificationChannel):
self.channel = channel
def send(self, recipient, message):
return self.channel.send(recipient, message)
class NotificationComposer:
def __init__(self, template: NotificationTemplate):
self.template = template
def compose(self, data):
return self.template.render(data)
# 服务协调
class NotificationService:
def __init__(self, sender: NotificationSender, composer: NotificationComposer):
self.sender = sender # 依赖抽象(DIP)
self.composer = composer
def notify(self, recipient, data):
message = self.composer.compose(data)
return self.sender.send(recipient, message)
SOLID原则对比
| 原则 | 关注点 | 核心规则 |
|---|---|---|
| SRP | 类的职责 | 一个类只有一个变化原因 |
| OCP | 扩展性 | 对扩展开放,对修改关闭 |
| LSP | 继承 | 子类可完全替换父类 |
| ISP | 接口设计 | 接口要小而专 |
| DIP | 依赖关系 | 依赖抽象而非具体 |
注意:SOLID原则要灵活应用,过度遵守会增加复杂度,根据实际场景平衡取舍。
要点总结
- SRP:一个类只有一个职责,变化原因单一,拆分大类
- OCP:通过抽象和策略模式实现扩展开放、修改关闭
- LSP:子类行为与父类一致,继承层次合理,不违反父类契约
- ISP:接口要小而专,客户端只依赖需要的接口
- DIP:高层依赖抽象接口,通过依赖注入传入具体实现
存放路径:articles/PYTHON/专家/架构与设计/SOLID原则实践.md
📝 发现内容有误?点击此处直接编辑