杂七杂八的砖 编程思想 IOC --- 控制反转 Fantasy-ke 2023-03-16 2023-03-16 引言 IOC,全称为 Inversion of Control(控制反转),是一种重要的编程思想,它可以帮助我们更好地管理程序中的依赖关系。在IOC的基础上,依赖注入(Dependency Injection,DI)是一种实现IOC的技术手段,它可以提高代码可测试性 ,可维护性 ,可拓展性 。
什么是IOC? 在传统的程序设计中,我们通常会使用直接依赖的方式来实现功能,这意味着我们需要自己创建并管理对象之间的依赖关系。这种方式有一个很明显的缺点,就是代码之间的耦合度非常高,一旦某个类发生了改变,所有依赖于它的类都需要修改。
而IOC则是一种反转控制的方式,它将对象的创建、依赖管理等控制权从程序员手中转移到了容器中,容器会根据配置信息来自动创建对象、管理依赖关系。这样做的好处在于,我们只需要关注自己的业务逻辑,而不需要关心对象的创建、销毁等底层细节
什么是依赖注入? 依赖注入是实现IOC的一种方式,它是指将对象所需要的依赖关系通过构造函数、属性、方法等方式传递给对象。通常情况下,我们会使用构造函数注入 、Setter方法注入 、接口注入 等方式来实现依赖注入。
以构造函数注入为例,我们可以将对象所需要的依赖关系通过构造函数的参数传递进来,这样做的好处在于,我们可以在对象创建的时候就确定它所依赖的对象,从而避免了后续修改依赖关系的麻烦。
直接依赖可能存在的问题 1. 高度耦合 在应用程序中使用紧密耦合的代码会导致代码难以维护和扩展。当一个组件的代码更改时,需要更改其他依赖于该组件的组件的代码。这种高度耦合的代码可能难以单元测试,因为测试需要创建所有依赖项的实例,这可能会使测试变得复杂
2. 硬编码依赖项 如果应用程序使用硬编码依赖项,即在代码中直接实例化依赖项,那么应用程序的可测试性将受到影响。这是因为在测试时,不可能轻松地用模拟对象或者桩来替换硬编码的依赖项,这样可能会使测试变得非常困难
3. 依赖项管理困难 如果没有使用IOC容器,那么将需要手动管理依赖项的生命周期,包括创建、初始化和销毁。这可能会使代码更加复杂,容易出错,也会导致代码可维护性的下降。
4. 缺乏灵活性 没有使用IOC,可能会导致应用程序的灵活性下降。因为依赖项在代码中硬编码,所以更改依赖项需要更改代码。而使用IOC,只需要更改配置即可更改依赖项,从而提高了应用程序的灵活性
5. 代码重复 如果没有使用IOC,那么可能会在应用程序中出现大量的重复代码。因为每个组件都需要手动实例化它们的依赖项,这可能导致重复的代码。而使用IOC,可以将依赖项的创建和管理交给IOC容器,从而避免代码重复
依赖注入的实现方式 依赖注入的实现方式有很多种,常见的有构造函数注入、Setter方法注入、接口注入等。
1. 构造函数注入 构造函数注入是最常见的依赖注入方式,它可以将对象所需要的依赖关系通过构造函数的参数传递进来。这种方式的好处在于,可以在对象创建的时候就确定它所依赖的对象,从而避免了后续修改依赖关系的麻烦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyService { private readonly ILogger _logger; private readonly IEmailService _emailService; public MyService (ILogger logger, IEmailService emailService ) { _logger = logger; _emailService = emailService; } public void DoSomething () { _logger.Log("DoSomething has been executed" ); _emailService.SendEmail("someone@example.com" , "DoSomething has been executed" ); } }
2. Setter方法注入 Setter方法注入是另一种常见的依赖注入方式,它可以将对象所需要的依赖关系通过Setter方法进行注入。这种方式的好处在于,可以将对象的依赖关系动态地进行修改,从而更加灵活地管理依赖关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MyService { private ILogger _logger; private IEmailService _emailService; public void SetLogger (ILogger logger ) { _logger = logger; } public void SetEmailService (IEmailService emailService ) { _emailService = emailService; } public void DoSomething () { _logger.Log("DoSomething has been executed" ); _emailService.SendEmail("someone@example.com" , "DoSomething has been executed" ); } }
3. 接口注入 接口注入是一种比较高级的依赖注入方式,它可以将对象所需要的依赖关系通过接口进行注入。这种方式的好处在于,可以通过接口来进行依赖管理,从而更加灵活地实现对象之间的交互。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public interface ILogger { void Log (string message ) ; } public interface IEmailService { void SendEmail (string toAddress, string message ) ; } public class MyService : IServiceDependency { private readonly ILogger _logger; private readonly IEmailService _emailService; public MyService (IServiceProvider serviceProvider ) { _logger = serviceProvider.GetService<ILogger>(); _emailService = serviceProvider.GetService<IEmailService>(); } public void DoSomething () { _logger.Log("DoSomething has been executed" ); _emailService.SendEmail("someone@example.com" , "DoSomething has been executed" ); } } public interface IServiceDependency { } public static class ServiceCollectionExtensions { public static IServiceCollection AddMyServices (this IServiceCollection services ) { services.AddSingleton<ILogger, ConsoleLogger>(); services.AddSingleton<IEmailService, SmtpEmailService>(); services.AddSingleton<IServiceDependency>(serviceProvider => new MyService(serviceProvider)); return services; } } public class Program { static void Main (string [] args ) { var services = new ServiceCollection(); services.AddMyServices(); using (var serviceProvider = services.BuildServiceProvider()) { var myService = serviceProvider.GetService<IServiceDependency>(); myService.DoSomething(); } } }
总结 依赖注入是一种实现IOC的技术手段,它可以帮助我们更好地管理程序中的依赖关系,降低代码耦合度,提高代码复用性和可测试性。当我们掌握了依赖注入的技术,就可以更加轻松地编写高质量、可维护的代码。