那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程
那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程作为技术博主我经常收到读者的提问“这些高大上的术语到底是什么意思它们之间有什么关系”今天我们就用最接地气的语言把这些概念掰开了、揉碎了讲清楚。## 从“硬编码依赖”说起想象你在开发一个“通知系统”需要发送邮件。最直接的做法是pythonclass EmailService: def send_email(self, message: str): print(f发送邮件: {message})class NotificationService: def send(self, message: str): email EmailService() # 直接创建依赖对象 email.send_email(message)这种写法的问题在于NotificationService 强依赖于 EmailService。未来如果想改用短信通知、微信通知就必须修改 NotificationService 的代码。这违反了“开闭原则”对扩展开放对修改关闭。## 第一招依赖倒置原则DIP依赖倒置原则的核心思想是高层模块不应该依赖低层模块两者都应该依赖抽象。翻译成人话就是不要依赖具体实现要依赖接口或抽象类。遵循 DIP 的代码应该是pythonfrom abc import ABC, abstractmethod# 1. 定义抽象接口class MessageSender(ABC): abstractmethod def send(self, message: str): pass# 2. 实现具体发送方式class EmailSender(MessageSender): def send(self, message: str): print(f发送邮件: {message})class SmsSender(MessageSender): def send(self, message: str): print(f发送短信: {message})# 3. 高层模块依赖抽象而非具体实现class NotificationService: def __init__(self, sender: MessageSender): # 依赖抽象接口 self.sender sender def send(self, message: str): self.sender.send(message)# 4. 使用notifier NotificationService(EmailSender()) # 可以轻松切换实现notifier.send(你好世界)看到变化了吗NotificationService 不再关心底层是邮件还是短信它只依赖MessageSender这个抽象。这就是面向接口编程——编程时面向接口抽象而不是面向实现。## 第二招控制反转IoC“控制反转”这个词听起来很玄乎其实它描述的是依赖的控制权转移。在传统代码中类自己控制依赖的创建new 对象在 IoC 中这个控制权被反转给了外部容器。用代码理解python# 传统方式类自己控制依赖class UserController: def __init__(self): self.user_service UserService() # 自己创建依赖 def get_user(self, user_id): return self.user_service.find_user(user_id)# IoC 方式依赖由外部注入class UserController: def __init__(self, user_service: UserService): # 控制权反转依赖由外部提供 self.user_service user_service def get_user(self, user_id): return self.user_service.find_user(user_id)控制反转的核心就是“别打电话给我我们会打给你”好莱坞原则。你的代码不再主动创建依赖而是等着被框架或容器“喂”依赖。## 第三招依赖注入DI依赖注入是实现控制反转的一种具体手段。常见的注入方式有三种1.构造器注入最推荐2.属性注入通过 setter3.方法注入通过参数看一个完整的 Python 示例pythonfrom abc import ABC, abstractmethod# 接口定义class Database(ABC): abstractmethod def query(self, sql: str): passclass MySQLDatabase(Database): def query(self, sql: str): print(f执行 MySQL 查询: {sql}) return [data1, data2]class PostgreSQLDatabase(Database): def query(self, sql: str): print(f执行 PostgreSQL 查询: {sql}) return [data3, data4]# 依赖注入容器简单实现class Container: def __init__(self): self._services {} def register(self, name, service): self._services[name] service def resolve(self, name): return self._services.get(name)# 业务类class UserRepository: def __init__(self, database: Database): self.db database # 构造器注入 def get_users(self): return self.db.query(SELECT * FROM users)# 使用容器container Container()container.register(db, MySQLDatabase()) # 注册具体实现container.register(user_repo, UserRepository(container.resolve(db)))repo container.resolve(user_repo)users repo.get_users()print(users)## 四者关系图解-面向接口编程是指导思想编程时依赖抽象-依赖倒置原则是设计原则高层不依赖低层都依赖抽象-控制反转是设计思想控制权交给容器-依赖注入是具体实现手段通过构造器、属性等注入依赖它们的关系就像菜谱原则→ 烹饪方法思想→ 具体操作手段→ 最终菜品代码。## 实战案例从混乱到优雅看看重构前后对比混乱版反面教材pythonclass OrderService: def __init__(self): self.logger FileLogger() # 强依赖 self.email Gmail() # 强依赖 self.db MySQL() # 强依赖 def process_order(self, order): self.logger.log(处理订单) self.db.save(order) self.email.send(订单确认)优雅版遵循原则pythonclass OrderService: def __init__(self, logger: Logger, notifier: Notifier, repo: Repository): self.logger logger self.notifier notifier self.repo repo def process_order(self, order): self.logger.log(处理订单) self.repo.save(order) self.notifier.send(订单确认)# 可以轻松切换实现order_service OrderService( loggerCloudLogger(), notifierWeChatNotifier(), repoMongoRepository())## 总结这些术语看起来很唬人但本质就一句话别让你的代码“硬编码”依赖让依赖通过接口传进来。这样做的好处1.可测试性可以轻松替换为 Mock 对象2.可维护性修改一个组件不影响其他组件3.可扩展性新增实现无需修改现有代码4.松耦合组件之间通过接口通信记住依赖倒置是原则控制反转是思想依赖注入是手段面向接口是方法。下次再听到这些词你就知道它们其实在说同一件事把依赖的控制权从你的代码中解放出来。