---
date: 2024-10-29 11:28
updated: 2024-12-03 21:44
tags:
  - 设计模式
share: "true"
link: "false"
---

# 概述

## 什么是依赖注入

**依赖注入**的核心思想是==将对象的依赖关系从类内部移到外部管理==。也就是说，**不是由类自己来创建它所依赖的对象，而是将这些依赖通过构造函数参数、方法参数或属性设置的方式传递给它**。这样，类不再负责依赖的创建和管理，而是依赖于外部注入，这样可以更方便地替换或修改依赖对象。

## 控制反转（Inversion of Control, IoC）

依赖注入是==控制反转（IoC）的一种实现方式==。IoC 是一种设计原则，指的是将对象的控制权从内部转移到外部。依赖注入通过构造函数、方法或属性注入的方式，实现了 IoC。

## 依赖注入的好处

- **低耦合度**：减少对象之间的直接依赖，提高代码的灵活性。
- **易于测试**：可以方便地替换依赖对象，进行单元测试。
- **高可维护性**：通过集中管理依赖，便于维护和扩展。

## 依赖注入的类型

依赖注入主要有以下几种方式：

- **构造函数注入（Constructor Injection）**：通过构造函数传递依赖。

- **方法注入（Method Injection）**：通过方法参数传递依赖。

- **属性注入（Property Injection）**：通过设置结构体的字段来传递依赖。

# 依赖注入解决了什么问题

## 硬编码的依赖

如果 `UserService` 自己创建了 `UserRepository`，就像这样：

```go
type UserService struct {
	repo *DatabaseUserRepository
}

func NewUserService() *UserService {
	return &UserService{
		repo: &DatabaseUserRepository{},  // 硬编码依赖
	}
}
```

这里的问题是：`UserService`**紧密依赖于**`DatabaseUserRepository`。如果你想要使用不同的 `UserRepository` 实现（例如，改成 `InMemoryUserRepository` 进行测试），就不得不修改 `UserService` 结构体中 `repo` 字段类型了。这种耦合降低了代码的灵活性和可测试性。

## 依赖注入的本质

依赖注入的精髓是将**对象所依赖的其他对象**从**类的内部**移到**类的外部**，并由外部来管理这些依赖。换句话说，**对象不再负责创建它的依赖，而是由外部传入这些依赖**。

```c
type UserService struct {
    repo UserRepository  // 依赖于接口，而不是具体实现
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}  // 依赖由外部传入
}
```

通过这种方式：

- `UserService` 不再依赖于特定的 `UserRepository` 实现，它只依赖于 `UserRepository` 接口。
- 依赖的具体实现（如 `DatabaseUserRepository` 或 `InMemoryUserRepository`）可以在运行时由外部决定，并通过构造函数传入。这就是**依赖注入**。

# C语言中的依赖注入

C语言本身并不直接支持依赖注入模式，因为它没有内置的面向对象编程特性，如C++或Java中的类和接口。然而，可以==通过结构体和函数指针来模拟依赖注入的概念==。

以下是一个简单的依赖注入示例，使用结构体和函数指针来定义依赖和提供注入点：

```c
#include <stdio.h>
#include <stdlib.h>
 
// 定义依赖接口
typedef struct {
    void (*print_message)(const char *message);
} Dependency;
 
// 依赖的实现
void print_message(const char *message) {
    printf("%s\n", message);
}
 
// 使用依赖的函数
void use_dependency(Dependency *dependency) {
    dependency->print_message("Hello, Dependency!");
}
 
int main() {
    // 创建依赖实例
    Dependency dependency;
    dependency.print_message = print_message;
 
    // 使用依赖
    use_dependency(&dependency);
 
    return 0;
}
```

在这个例子中，`Dependency` 结构体定义了一个打印消息的==函数指针==。`print_message` 函数实现了这个接口。`use_dependency` 函数接受一个`Dependency`结构体指针作为参数，并调用其`print_message`方法。在`main`函数中，我们创建了一个`Dependency`实例并设置了它的`print_message`方法指针指向`print_message`函数的实现。

这个例子展示了如何在不修改`use_dependency`函数代码的情况下，通过结构体和函数指针来动态注入依赖。虽然这不是面向对象编程中的依赖注入，但在C语言中可以通过这种方式模拟依赖注入。

# C++简单实现依赖注入

在C++中实现依赖注入的一种简单方式是使用工厂模式和配置文件。以下是一个简单的例子：

```c++
#include <iostream>
#include <map>
#include <string>
#include <memory>
 
// 抽象基类
class BaseClass {
public:
    virtual void Show() = 0;
    virtual ~BaseClass() = default;
};
 
// 实现类A
class ClassA : public BaseClass {
public:
    void Show() override {
        std::cout << "Class A" << std::endl;
    }
};
 
// 实现类B
class ClassB : public BaseClass {
public:
    void Show() override {
        std::cout << "Class B" << std::endl;
    }
};
 
// 工厂类
class Factory {
public:
    BaseClass* Create(const std::string& type) {
        if (creators.find(type) != creators.end()) {
            return creators[type]();
        }
        return nullptr;
    }
 
    void Register(const std::string& type, std::function<BaseClass*()> creator) {
        creators[type] = creator;
    }
 
private:
    std::map<std::string, std::function<BaseClass*()>> creators;
};
 
// 使用工厂
int main() {
    Factory factory;
 
    // 注册类
    factory.Register("A", []() { return new ClassA(); });
    factory.Register("B", []() { return new ClassB(); });
 
    // 创建对象
    BaseClass* obj = factory.Create("A");
    if (obj) {
        obj->Show();
        delete obj;
    }
 
    return 0;
}
```

这段代码定义了一个基类`BaseClass`和两个实现类`ClassA`和`ClassB`。`Factory`类负责创建对象，通过`Register`方法将类型与创建对象的函数绑定，通过`Create`方法根据类型创建对象。

在`main`函数中，我们注册了两个实现类，并根据提供的类型字符串创建了一个对象。这个例子展示了依赖注入的简单实现，但在实际应用中可能需要考虑内存管理、多线程和异常处理等问题。
