Menu
CoddyTech

依赖注入基础

本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 46 课,共 70 课。

依赖注入(DI)是一种技术,类通过外部获取依赖项,而不是自己创建它们。这样可以让代码更灵活、更易于测试,并降低耦合度。

不使用 DI 时,类会自行创建依赖项,因此很难更改或测试:

public class OrderService
{
    private EmailSender sender = new EmailSender();  // 紧密耦合
    
    public void PlaceOrder() => sender.Send("Order placed");
}

使用 DI 时,依赖项会通过构造函数传入:

public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine("Email: " + message);
}

public class OrderService
{
    private IMessageSender sender;
    
    public OrderService(IMessageSender sender)
    {
        this.sender = sender;
    }
    
    public void PlaceOrder() => sender.Send("Order placed");
}

现在,OrderService 不知道也不关心它使用的是电子邮件、短信,还是用于测试的模拟发送器。创建对象时,会“注入”这个依赖项:

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // 邮件:订单已提交

这种模式遵循依赖抽象(接口)而非具体实现的原则。这样一来,你可以替换实现,而无需修改使用它的类。这对于单元测试和维护灵活的架构至关重要。

challenge icon

挑战

简单

让我们构建一个日志系统,展示依赖注入的强大之处。你将创建一个灵活的 Logger 类,而不是硬编码日志的写入方式。这个类可以与任何日志目标配合使用,例如控制台、模拟文件或其他目标,而不需要了解具体细节。

你将把代码分到三个文件中:

  • ILogWriter.cs:在 Logging 命名空间中定义一个名为 ILogWriter 的接口。此接口规定了任何日志目标都必须遵循的契约,并且应包含一个 Write(string message) 方法,该方法返回一个字符串,用于说明消息写入了哪里。
  • LogWriters.cs:在 Logging 命名空间中创建 ILogWriter 的两个具体实现:
    • ConsoleLogWriter:它的 Write 方法返回 Console: {message}
    • FileLogWriter:通过构造函数接收文件名,它的 Write 方法返回 File[{filename}]: {message}
  • Logger.cs:在 Logging 命名空间中创建一个 Logger 类,通过构造函数注入接收依赖。Logger 应该:
    • 通过构造函数接收一个 ILogWriter,并将其存储在私有字段中
    • 包含一个 Log(string message) 方法,该方法调用注入的写入器,并返回结果
  • Program.cs:通过创建两个不同的日志记录器来演示依赖注入,一个使用 ConsoleLogWriter,另一个使用 FileLogWriter。展示同一个 Logger 类如何根据注入的依赖不同而表现不同。

你将收到两个输入:

  • 文件日志记录器的文件名
  • 要记录的消息

创建一个使用 ConsoleLogWriter 的 Logger,并记录这条消息。然后创建另一个使用 FileLogWriter 的 Logger(使用输入中的文件名),并记录同一条消息。将每个结果分别打印在单独的一行上。

例如,如果输入是 app.log 和 System started,输出应为:

Console: System started
File[app.log]: System started

注意,Logger 类从未改变,它可以与任何 ILogWriter 实现配合使用。这就是依赖注入的核心:Logger 依赖于一个抽象(接口),而具体实现则从外部“注入”。之后,你可以轻松添加 DatabaseLogWriter 或 CloudLogWriter,而完全无需修改 Logger 类!

亲手试试

using System;
using Logging;

class Program
{
    public static void Main(string[] args)
    {
        // 读取输入
        string filename = Console.ReadLine();
        string message = Console.ReadLine();

        // TODO:使用 ConsoleLogWriter 创建一个 Logger,并记录消息
        // 打印结果

        // TODO:使用 FileLogWriter(使用 filename)创建另一个 Logger,并记录相同的消息
        // 打印结果
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C# 在线编译器