什么是依赖和耦合

不同的类与对象,执行不同的功能,大家分工合作,各司其职。
在代码世界里,这个合作,有个术语,叫做【依赖】,依赖的同时就出现了耦合,依赖越直接,耦合越紧密

这个代码就是紧耦合,Car完全是依赖在Engine上的。Engine出问题,Car完蛋。 如果十个人开发,其中9个人的代码都依赖另一个人的代码才能运行,要是那个人没开发好,代码bug,9个人都完蛋。 所以开发时,尽可能避免紧耦合

  1. namespace ConsoleApp2
  2. {
  3. //引擎
  4. class Engine
  5. {
  6. public int RPM { get; private set; }//发动机每分钟多少转
  7. public void Work(int gas)
  8. {
  9. this.RPM = 1000 * gas;
  10. }
  11. }
  12. //汽车
  13. class Car
  14. {
  15. private Engine engine;
  16. public int Speed { get; private set; }
  17. public Car(Engine engine)//创建实例时,必须给个引擎,没有引擎哪叫车
  18. {
  19. this.engine = engine;
  20. }
  21. public void Run(int gas)
  22. {
  23. this.engine.Work(gas);
  24. this.Speed = this.engine.RPM / 100;
  25. }
  26. }
  27. }
  1. namespace ConsoleApp2
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. Engine engine = new Engine();
  8. Car car = new Car(engine);
  9. car.Run(3);
  10. Console.WriteLine(car.Speed);
  11. }
  12. }
  13. }

依赖倒置(DIP)

  1. 高层次的模块不应该依赖低层次的模块,两者都应该依赖其抽象。

    高层模块不应该直接依赖低层模块的具体实现,而是应该依赖于低层模块的抽象,也就是说,模块之间的依赖是通过抽象发生的,实现类之间不应该发生直接的依赖关系,他们的依赖关系应该通过接口或者抽象类产生。 比如上面的汽车引擎案例:高层模块Car,低层模块Engine,不应该Car依赖具体的Engine对象,应该依赖Engine的抽象基类或者接口。

  2. 抽象不应该依赖于具体,具体应该依赖于抽象。

    接口或者抽象类不应该依赖于实现类。举个例子,假如我们要写xxx类代码,直接就去实现了功能,等到开发完成以后发现没有使用依赖倒置原则,这时候在根据实现类去写接口,这种是不对的,应该首先设计抽象,然后在根据抽象去实现,应该要面向接口编程。先写接口或者抽象类,在写实现或者子类,这叫设计。反过来叫重构。

通过接口实现依赖倒置

  1. namespace ConsoleApp2
  2. {
  3. interface IPhone
  4. {
  5. void Dail();//拨号
  6. void PickUp();//接电话
  7. void Send();//发短信
  8. void Receive();//收短信
  9. }
  10. class Nokia : IPhone
  11. {
  12. public void Dail()
  13. {
  14. Console.WriteLine("洛基亚打电话");
  15. }
  16. public void PickUp()
  17. {
  18. Console.WriteLine("洛基亚接电话");
  19. }
  20. public void Receive()
  21. {
  22. Console.WriteLine("洛基亚收短信");
  23. }
  24. public void Send()
  25. {
  26. Console.WriteLine("洛基亚发短信");
  27. }
  28. }
  29. class HaWei : IPhone
  30. {
  31. public void Dail()
  32. {
  33. Console.WriteLine("华为打电话");
  34. }
  35. public void PickUp()
  36. {
  37. Console.WriteLine("华为接电话");
  38. }
  39. public void Receive()
  40. {
  41. Console.WriteLine("华为收短信");
  42. }
  43. public void Send()
  44. {
  45. Console.WriteLine("华为发短信");
  46. }
  47. }
  48. class PhoneUser
  49. {
  50. private IPhone phone;
  51. public PhoneUser(IPhone phone)
  52. {
  53. this.phone = phone;
  54. }
  55. public void UsePhone()
  56. {
  57. phone.Dail();
  58. phone.PickUp();
  59. phone.Send();
  60. phone.Receive();
  61. }
  62. }
  63. }
  1. namespace ConsoleApp2
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. PhoneUser phoneUser = new PhoneUser(new Nokia());
  8. phoneUser.UsePhone();
  9. //如果用户的洛基亚手机坏了,直接一个手机,换成华为的
  10. phoneUser = new PhoneUser(new HaWei());
  11. phoneUser.UsePhone();
  12. //只要PhoneUser里面的逻辑没问题,如果手机对象的逻辑出问题了,换一个手机对象。
  13. //不用修改PhoneUser里面的代码,用接口实现了多态,解耦合
  14. }
  15. }
  16. }

控制反转(IOC)

控制反转(IOC):Inversion of Control的缩写,一种反转流、依赖和接口的方式,它把传统上由程序代码直接操控的对象的控制器(创建、维护)交给第三方,通过第三方(IOC容器)来实现对象组件的装配和管理。
IOC容器,也可以叫依赖注入框架,是由一种依赖注入框架提供的,主要用来映射依赖,管理对象的创建和生存周期。IOC容器本质上就是一个对象,通常会把程序里面所有的类都注册进去,使用这个类的时候,直接从容器里面去解析。

依赖注入(DI)

依赖注入(DI):Dependency Injection的缩写。依赖注入是控制反转的一种实现方式,依赖注入的目的就是为了实现控制反转。
依赖注入是一种工具或手段,目的是帮助我们开发出松耦合、可维护的程序。

演示类

关于这个演示类,看接口文章

  1. namespace ConsoleApp2
  2. {
  3. //交通工具接口
  4. interface IVehicle
  5. {
  6. void Run();
  7. }
  8. //武器接口
  9. interface IWeapon
  10. {
  11. void Fire();
  12. }
  13. //飞行接口
  14. interface IFly
  15. {
  16. void Fly();
  17. }
  18. //坦克接口,实现了交通工具的功能,还实现了武器的功能
  19. interface ITank:IVehicle,IWeapon
  20. {
  21. }
  22. //战斗机接口
  23. interface IFighter : IVehicle, IWeapon, IFly
  24. {
  25. }
  26. //汽车
  27. class Car : IVehicle
  28. {
  29. public void Run()
  30. {
  31. Console.WriteLine("汽车跑");
  32. }
  33. }
  34. //坦克
  35. class Tank : ITank
  36. {
  37. public void Fire()
  38. {
  39. Console.WriteLine("坦克开火");
  40. }
  41. public void Run()
  42. {
  43. Console.WriteLine("坦克跑");
  44. }
  45. }
  46. //战斗机
  47. class Fighter : IFighter
  48. {
  49. public void Fire()
  50. {
  51. Console.WriteLine("飞机开火");
  52. }
  53. public void Fly()
  54. {
  55. Console.WriteLine("飞机飞");
  56. }
  57. public void Run()
  58. {
  59. Console.WriteLine("飞机跑");
  60. }
  61. }
  62. //驾驶人
  63. class Person
  64. {
  65. //只要一个交通工具,不管是车,坦克还是飞机。IVehicle是这些的基接口
  66. private IVehicle vehicle;
  67. public Person(IVehicle vehicle)
  68. {
  69. this.vehicle = vehicle;
  70. }
  71. public void Drive()
  72. {
  73. //我只需要跑的功能
  74. vehicle.Run();
  75. }
  76. public void Attack()
  77. {
  78. //如果要使用Fire,接口就不能是基接口IVehicle了,因为他没有,得是IWeapon
  79. //凡是继承了IWeapon接口的都能用
  80. }
  81. }
  82. }

安装框架

依赖注入 要用 依赖注入框架,框架很多,这里使用其中一个

NuGet搜索:DependencyInjection

image.png

引入名称空间

  1. using Microsoft.Extensions.DependencyInjection;

创建依赖注入

创建依赖注入程序只用执行一次,但是要使用,sp一定要在一个公共的能访问都的地方

  1. static void Main(string[] args)
  2. {
  3. //创建一个服务的提供者容器
  4. var sc = new ServiceCollection();
  5. //往容器里面装东西,参数1:基接口类型,参数2:实现类类型,创建依赖关系
  6. sc.AddScoped(typeof(IWeapon), typeof(Tank));
  7. //注册,要使用依赖注入,sp一定是要能访问到地方
  8. var sp = sc.BuildServiceProvider();
  9. }

简单使用依赖注入

  1. static void Main(string[] args)
  2. {
  3. //创建一个服务的提供者容器
  4. var sc = new ServiceCollection();
  5. //往容器里面装东西,参数1:基接口类型,参数2:实现类类型
  6. sc.AddScoped(typeof(IWeapon), typeof(Tank));
  7. //注册
  8. var sp = sc.BuildServiceProvider();
  9. //----------------------------------------
  10. //使用依赖注入
  11. //从容器里面取出一个添加的对象,基接口类型,引用的是实现类的对象
  12. //这样就获取了一个tank对象,能只能tank对象的一切功能
  13. IWeapon tank = sp.GetService<IWeapon>();
  14. tank.Fire();
  15. }

使用依赖注入的好处

如果一个项目很多个文件里面创建了很多个 new Tank();对象。如果需求改变了,不要Tank了,全部改成Fighter。那只能去一个一个把new Tank();改成new Fighter();了。

用了依赖注入,反正接收的类型都是IWeapon,Tank和Fighter的基接口。直接改变ServiceCollection里面的参数后,凡是使用这个依赖注入的地方,都变成了,我改变的那个

  1. static void Main(string[] args)
  2. {
  3. var sc = new ServiceCollection();
  4. sc.AddScoped(typeof(IWeapon), typeof(Fighter));
  5. var sp = sc.BuildServiceProvider();
  6. //----------------------------------------
  7. //变量名我都懒得改,我只改了上面的Fighter,其他都没动。
  8. //整个代码中sp.GetService<IWeapon>()的都变成了战斗机
  9. IWeapon tank = sp.GetService<IWeapon>();
  10. tank.Fire();//飞机开火
  11. }

升级点的注入用法

同样的,只要我改变了创建依赖注入时 容器里的参数,凡是使用依赖注入的都跟着自动变。不用手动去改代码。多态的概念

  1. static void Main(string[] args)
  2. {
  3. var sc = new ServiceCollection();
  4. //两种方式都可以
  5. sc.AddScoped(typeof(IWeapon), typeof(Fighter));
  6. sc.AddScoped<IVehicle, Car>();
  7. //传入一个类
  8. //这个类实例化时需要传入一个IVehicle对象
  9. //sc.AddScoped<Person>();
  10. sc.AddScoped(typeof(Person));
  11. var sp = sc.BuildServiceProvider();
  12. //----------------------------------------
  13. //调用时,不需要手动添加实现类,因为系统会自动在容器里面找IVehicle接口,找到了,发现实现类是Car
  14. //所以给Person传入的就是Car对象,把Car注入进了Person
  15. var p = sp.GetService<Person>();
  16. p.Drive();//车在跑
  17. }

服务的生命周期

就是Add后面的半截方法名

Transient:瞬时的,每次被都会生成一个新的实例 Scoped:范围的,在一个范围内是同一个对象,这个返回可以由程序员来定义。ASP.NET是由框架定义的,一次web请求产生一个实例,web请求被处理完生命周期就截止了 Singleton:单例的,这个服务的实例一旦被创建,以后用这个服务的时候都会只用这一个实例,会一直存活到这个项目停止运行

手动范围Scoped

  1. var sc = new ServiceCollection();
  2. sc.AddScoped(typeof(IWeapon), typeof(Fighter));
  3. var sp = sc.BuildServiceProvider();
  4. using(IServiceScope scope = sp.CreateScope())
  5. {
  6. //在这个using范围里面获取的对象都是同一个
  7. IWeapon tank = scope.ServiceProvider.GetService<IWeapon>();
  8. }

.NET的依赖注入

上面那种方式其实不算是真正的依赖注入,算是服务定位器。

  1. 如果一个类是通过依赖注入创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被依赖注入赋值。如果是程序员手动创建的对象,这个对象就和依赖注入没有关系,也不会自动赋值。
  2. .NET的依赖注入默认是构造函数注入。

    1. /// <summary>
    2. /// 日志服务
    3. /// </summary>
    4. public interface ILog
    5. {
    6. void log(string message);
    7. }
    8. public class Log4Net : ILog
    9. {
    10. public void log(string message)
    11. {
    12. Console.WriteLine("Log4Net:"+message);
    13. }
    14. }
    15. public class NLog : ILog
    16. {
    17. public void log(string message)
    18. {
    19. Console.WriteLine("NLog:"+message);
    20. }
    21. }
    1. /// <summary>
    2. /// 云存储服务
    3. /// </summary>
    4. public interface ICloud
    5. {
    6. void save(string content);
    7. }
    8. public class AliCloud : ICloud
    9. {
    10. private readonly IConfig config;
    11. public AliCloud(IConfig config)
    12. {
    13. this.config = config;
    14. }
    15. public void save(string content)
    16. {
    17. config.getConfig();
    18. Console.WriteLine("Ali:"+ content);
    19. }
    20. }
    21. public class TenCloud : ICloud
    22. {
    23. private readonly IConfig config;
    24. public TenCloud(IConfig config)
    25. {
    26. this.config = config;
    27. }
    28. public void save(string content)
    29. {
    30. config.getConfig();
    31. Console.WriteLine("Ten:" + content);
    32. }
    33. }
    1. /// <summary>
    2. /// 配置服务
    3. /// </summary>
    4. public interface IConfig
    5. {
    6. void getConfig();
    7. }
    8. public class JsonConfig : IConfig
    9. {
    10. public void getConfig()
    11. {
    12. Console.WriteLine("JsonConfig");
    13. }
    14. }
    15. public class DbConfig : IConfig
    16. {
    17. public void getConfig()
    18. {
    19. Console.WriteLine("DbConfig");
    20. }
    21. }
    1. public class Controller
    2. {
    3. private readonly ILog log;
    4. private readonly ICloud cloud;
    5. public Controller(ILog log, ICloud cloud)
    6. {
    7. this.log = log;
    8. this.cloud = cloud;
    9. }
    10. public void Test()
    11. {
    12. log.log("开始记录日志");
    13. cloud.save("存储信息");
    14. log.log("结束记录日志");
    15. }
    16. }
    1. static void Main(string[] args)
    2. {
    3. //创建一个服务的提供者容器
    4. var sc = new ServiceCollection();
    5. sc.AddScoped<ILog,Log4Net>();
    6. sc.AddScoped<IConfig, JsonConfig>();
    7. //如果我要换一个云存储服务,直接把注册的这个AliCloud换成TenCloud就行了,一位内他们都实现了ICloud。不管具体实现是那个云存储,只要是ICloud就行了
    8. //sc.AddScoped<ICloud, AliCloud>();
    9. sc.AddScoped<ICloud, TenCloud>();
    10. sc.AddScoped<Controller>();
    11. //获取到控制器,调用业务代码,会根据里面的构造函数进行依赖注入。前提是注册过这个服务
    12. //在ASP.NET中,都不用这一步GetService,顶层的Controller对接前端。也不需要用这种方式拿到对象。会自动创建的。
    13. Controller controller = sc.BuildServiceProvider().GetRequiredService<Controller>();
    14. controller.test();
    15. }

    扩展方法依赖注入

    像上面那样的添加依赖注入看起来不够简洁。可以使用扩展方法

    1. namespace Microsoft.Extensions.DependencyInjection
    2. {
    3. public static class LogExtensions
    4. {
    5. //调用这个AddLog方法的时候,不用传递参数。当然形参列表可以添加参数,调用的时候根据后面的参数进行传递
    6. public static void AddLog(this IServiceCollection services)
    7. {
    8. services.AddScoped<ILog, Log4Net>();
    9. }
    10. }
    11. }
    1. //创建一个服务的提供者容器
    2. var sc = new ServiceCollection();
    3. //sc.AddScoped<ILog,Log4Net>();
    4. sc.AddLog();