依赖注入

前言

本文为依赖注入的学习笔记,参考了Go语言:一文看懂什么是DI依赖注入(dependency injection)设计模式


DI依赖注入(dependency injection)设计模式

什么是DI

  在理解它在编程中的含义之前,首先让我们了解一下它的总体含义,这可以帮助我们更好地理解这个概念。

  依赖是指依靠某种东西来获得支持。比如我会说我们对手机的依赖程度过高。

  在讨论依赖注入之前,我们先理解编程中的依赖是什么意思。

  当 class A 使用 class B 的某些功能时,则表示 class A 具有 class B 依赖。

  在 Java 中,在使用其他 class 的方法之前,我们首先需要创建那个 class 的对象(即 class A 需要创建一个 class B 实例)。

  因此,将创建对象的任务转移给其他 class,并直接使用依赖项的过程,被称为“依赖项注入”。

  依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除Java类之间的依赖关系,实现松耦合,以便于开发测试。为了更好地理解DI,先了解DI要解决的问题。

我们先用Java代码理解一下普遍的情况:

耦合太紧的问题

  如果使用一个类,自然的做法是创建一个类的实例:

class Player{  
    Weapon weapon;  
    Player(){          // 与 Sword类紧密耦合
        this.weapon = new Sword();  
    }      public void attack() {
        weapon.attack();
    }
}  

  这个方法存在耦合太紧的问题,例如,玩家的武器只能是剑Sword,而不能把Sword替换成枪Gun。要把Sword改为Gun,所有涉及到的代码都要修改,当然在代码规模小的时候这根本就不是什么问题,但代码规模很大时,就会费时费力了。

依赖注入(DI)过程

  依赖注入是一种消除类之间依赖关系的设计模式。例如,A类要依赖B类,A类不再直接创建B类,而是把这种依赖关系配置在外部xml文件(或java config文件)中,然后由Spring容器根据配置信息创建、管理bean类。

示例:

class Player{  
    Weapon weapon;      // weapon 被注入进来
    Player(Weapon weapon){          this.weapon = weapon;  
    }      public void attack() {
        weapon.attack();
    }    public void setWeapon(Weapon weapon){          this.weapon = weapon;  
    }  
}   

  如上所示,Weapon类的实例并不在代码中创建,而是外部通过构造函数传入,传入类型是父类Weapon,所以传入的对象类型可以是任何Weapon子类。

  传入哪个子类,可以在外部xml文件(或者java config文件)中配置,Spring容器根据配置信息创建所需子类实例,并注入Player类中,如下所示:

<bean id="player" class="com.qikegu.demo.Player"> 
    <construct-arg ref="weapon"/>
</bean>

<bean id="weapon" class="com.qikegu.demo.Gun"> 
</bean>

  上面代码中<construct-arg ref="weapon"/> ref指向id="weapon"的bean,传入的武器类型是Gun,如果想改为Sword,可以作如下修改:

  <bean id="weapon" class="com.qikegu.demo.Sword"> 
  </bean>

  注意:松耦合,并不是不要耦合。A类依赖B类,A类和B类之间存在紧密耦合,如果把依赖关系变为A类依赖B的父类B0类,在A类与B0类的依赖关系下,A类可使用B0类的任意子类,A类与B0类的子类之间的依赖关系是松耦合的。

python 依赖注入

在 Python 中,依赖注入(Dependency Injection, DI) 的核心思想与 Java 或其他语言类似,即通过外部将依赖项传递给需要它们的对象,而不是由对象自己创建依赖。Python 的灵活性(动态类型、无强制接口等)使得实现依赖注入更加灵活,但核心目标仍然是解耦组件、提高可测试性、增强代码可维护性。
Python 中依赖注入的核心概念

依赖(Dependency)

当一个类(或函数)需要另一个类(或对象)的功能时,后者就是前者的依赖。例如:

class Database:
    def query(self):
        return "Data from DB"

class Service:
    def __init__(self):
        self.db = Database()  # Service 依赖 Database
    def get_data(self):
        return self.db.query()

这里,Service 类直接创建了 Database 对象,导致两者高度耦合。

依赖注入(DI)

将依赖项的创建和管理交给外部,通过参数、属性或方法传递给需要它们的对象。例如:

class Service:
    def __init__(self, db):  # 通过构造函数注入依赖
        self.db = db
    def get_data(self):
        return self.db.query()

现在,Service 不再自己创建 Database,而是由外部传入。

Python 中实现依赖注入的常见方式

以下是 Python 中实现依赖注入的典型方法:

  1. 构造函数注入(Constructor Injection)

通过类的构造函数传递依赖项,是最常见的方式:

# 依赖项

class Database:
    def query(self):
        return "Real data"

# 使用依赖项的类

class Service:
    def __init__(self, db):  # 通过构造函数注入
        self.db = db
    def get_data(self):
        return self.db.query()

# 使用时由外部传入依赖

db = Database()
service = Service(db)  # 依赖注入完成
print(service.get_data())  # 输出:Real data
  1. 属性注入(Setter Injection)

通过类的属性或方法动态设置依赖项:

class Service:
    def __init__(self):
        self.db = None  # 初始无依赖

    def set_db(self, db):  # 通过 setter 方法注入
        self.db = db

    def get_data(self):
        if self.db is None:
            raise ValueError("Database dependency not set")
        return self.db.query()

# 使用时动态注入依赖

service = Service()
service.set_db(Database())  # 依赖注入完成
print(service.get_data())  # 输出:Real data
  1. 方法注入(Method Injection)

在调用方法时直接传递依赖项:

class Service:
    def get_data(self, db):  # 依赖直接作为参数传入
        return db.query()

db = Database()
service = Service()
print(service.get_data(db))  # 输出:Real data
  1. 装饰器注入(Decorator Injection)

利用 Python 的装饰器特性,在函数或方法调用时动态注入依赖:

def inject_db(func):
    def wrapper(*args, **kwargs):
        db = Database()  # 或从容器获取依赖
        return func(db, *args, **kwargs)  # 将依赖作为参数传入
    return wrapper

class Service:
    @inject_db
    def get_data(self, db):  # 依赖由装饰器注入
        return db.query()

service = Service()
print(service.get_data())  # 输出:Real data

Python 中依赖注入的优势

  1. 解耦组件
    类不再直接依赖具体实现,而是依赖接口或抽象类型。例如:

     class DatabaseInterface:
         def query(self):
             pass
    
     class RealDatabase(DatabaseInterface):
         def query(self):
             return "Real data"
    
     class MockDatabase(DatabaseInterface):
         def query(self):
             return "Mock data"
    
     class Service:
         def __init__(self, db: DatabaseInterface):  # 依赖接口而非具体类
             self.db = db
    

    这样,Service 可以接受任何实现 DatabaseInterface 的类(如 RealDatabase 或 MockDatabase)。

  2. 提高可测试性
    可以轻松替换依赖项为模拟对象(Mock):

    # 测试时注入 Mock 依赖
    
    mock_db = MockDatabase()
    service = Service(mock_db)
    assert service.get_data() == "Mock data"
    
  3. 灵活性
    Python 的动态特性允许在运行时动态修改依赖,例如:

    service = Service(RealDatabase())
    service.set_db(MockDatabase())  # 运行时切换依赖
    

Python 中的依赖注入框架

虽然 Python 可以通过纯代码手动实现依赖注入,但框架可以简化复杂系统的管理。常用框架包括:

  1. FastAPI 的依赖注入

    FastAPI 内置了依赖注入机制,常用于 Web 开发:

    from fastapi import Depends
    
    def get_db():
        return Database()
    
    class Service:
        def __init__(self, db):
            self.db = db
    
    @app.get("/data")
    def read_data(service: Service = Depends(lambda: Service(get_db()))):
        return service.get_data()
    
  2. 其他框架

    • injector:基于接口的依赖注入框架。
    • Pydantic:结合类型注解实现依赖注入(如通过 Field 和工厂模式)。

FASTAPI中的依赖注入

在 FastAPI 中,无需通过复杂的类或额外的注册步骤来声明依赖项,只需要将依赖项函数直接传递给 Depends,FastAPI 就能自动识别并处理它。具体来说:

  • 无需创建专门的类:
    在其他框架(如某些 Java/Spring 框架)中,依赖项可能需要定义为类,并通过 @Component 或其他注解“注册”到框架中,才能被注入。
    但在 FastAPI 中,依赖项只是一个普通的函数或异步函数,不需要额外的类或装饰器。你只需要定义一个函数,然后通过 Depends 直接引用它即可。

  • 无需显式注册到 FastAPI:
    其他框架可能需要将依赖项“注册”到应用实例中(例如通过 app.add_dependency() 或类似方法)。
    但在 FastAPI 中,只要你在路径操作函数的参数中使用 Depends(依赖项函数),FastAPI 会自动识别并执行该依赖项,无需任何额外配置。

示例对比
FastAPI 的简单方式

from fastapi import FastAPI, Depends

app = FastAPI()

# 定义依赖项:只需要一个普通函数

def get_db():
    return "Database Connection"

# 直接在路径操作函数中通过 Depends 引用依赖项

@app.get("/items/")
def read_items(db=Depends(get_db)):
    return {"db": db}

其他框架的典型方式(对比)

# 假设是某个需要注册的框架

from my_framework import App, Component

# 需要定义一个类作为依赖项

class DatabaseComponent:
    def get_db(self):
        return "Database Connection"

# 需要显式注册到框架中

app = App()
app.register_component(DatabaseComponent())

# 在路径操作中注入

@app.get("/items/")
def read_items(db=Inject(DatabaseComponent).get_db()):
    return {"db": db}

为什么 FastAPI 这样设计?

  1. 简洁性:
    FastAPI 的设计理念是“代码第一,配置最少”。通过直接传递函数给 Depends,开发者无需关心底层的依赖管理,只需关注业务逻辑。

  2. 灵活性:
    依赖项可以是同步函数、异步函数,甚至其他依赖项的组合,而无需额外的类约束。

  3. 自动解析参数:
    FastAPI 会自动解析依赖项的参数,例如从请求查询参数、路径参数、Header 等中获取值,就像处理路径操作函数一样。

posted @ 2025-03-17 23:19  玉米面手雷王  阅读(31)  评论(0)    收藏  举报