什么是依赖和耦合
不同的类与对象,执行不同的功能,大家分工合作,各司其职。
在代码世界里,这个合作,有个术语,叫做【依赖】,依赖的同时就出现了耦合,依赖越直接,耦合越紧密
这个代码就是紧耦合,Car完全是依赖在Engine上的。Engine出问题,Car完蛋。 如果十个人开发,其中9个人的代码都依赖另一个人的代码才能运行,要是那个人没开发好,代码bug,9个人都完蛋。 所以开发时,尽可能避免紧耦合
namespace ConsoleApp2{//引擎class Engine{public int RPM { get; private set; }//发动机每分钟多少转public void Work(int gas){this.RPM = 1000 * gas;}}//汽车class Car{private Engine engine;public int Speed { get; private set; }public Car(Engine engine)//创建实例时,必须给个引擎,没有引擎哪叫车{this.engine = engine;}public void Run(int gas){this.engine.Work(gas);this.Speed = this.engine.RPM / 100;}}}
namespace ConsoleApp2{class Program{static void Main(string[] args){Engine engine = new Engine();Car car = new Car(engine);car.Run(3);Console.WriteLine(car.Speed);}}}
依赖倒置(DIP)
高层次的模块不应该依赖低层次的模块,两者都应该依赖其抽象。
高层模块不应该直接依赖低层模块的具体实现,而是应该依赖于低层模块的抽象,也就是说,模块之间的依赖是通过抽象发生的,实现类之间不应该发生直接的依赖关系,他们的依赖关系应该通过接口或者抽象类产生。 比如上面的汽车引擎案例:高层模块Car,低层模块Engine,不应该Car依赖具体的Engine对象,应该依赖Engine的抽象基类或者接口。
抽象不应该依赖于具体,具体应该依赖于抽象。
接口或者抽象类不应该依赖于实现类。举个例子,假如我们要写xxx类代码,直接就去实现了功能,等到开发完成以后发现没有使用依赖倒置原则,这时候在根据实现类去写接口,这种是不对的,应该首先设计抽象,然后在根据抽象去实现,应该要面向接口编程。先写接口或者抽象类,在写实现或者子类,这叫设计。反过来叫重构。
通过接口实现依赖倒置
namespace ConsoleApp2{interface IPhone{void Dail();//拨号void PickUp();//接电话void Send();//发短信void Receive();//收短信}class Nokia : IPhone{public void Dail(){Console.WriteLine("洛基亚打电话");}public void PickUp(){Console.WriteLine("洛基亚接电话");}public void Receive(){Console.WriteLine("洛基亚收短信");}public void Send(){Console.WriteLine("洛基亚发短信");}}class HaWei : IPhone{public void Dail(){Console.WriteLine("华为打电话");}public void PickUp(){Console.WriteLine("华为接电话");}public void Receive(){Console.WriteLine("华为收短信");}public void Send(){Console.WriteLine("华为发短信");}}class PhoneUser{private IPhone phone;public PhoneUser(IPhone phone){this.phone = phone;}public void UsePhone(){phone.Dail();phone.PickUp();phone.Send();phone.Receive();}}}
namespace ConsoleApp2{class Program{static void Main(string[] args){PhoneUser phoneUser = new PhoneUser(new Nokia());phoneUser.UsePhone();//如果用户的洛基亚手机坏了,直接一个手机,换成华为的phoneUser = new PhoneUser(new HaWei());phoneUser.UsePhone();//只要PhoneUser里面的逻辑没问题,如果手机对象的逻辑出问题了,换一个手机对象。//不用修改PhoneUser里面的代码,用接口实现了多态,解耦合}}}
控制反转(IOC)
控制反转(IOC):Inversion of Control的缩写,一种反转流、依赖和接口的方式,它把传统上由程序代码直接操控的对象的控制器(创建、维护)交给第三方,通过第三方(IOC容器)来实现对象组件的装配和管理。
IOC容器,也可以叫依赖注入框架,是由一种依赖注入框架提供的,主要用来映射依赖,管理对象的创建和生存周期。IOC容器本质上就是一个对象,通常会把程序里面所有的类都注册进去,使用这个类的时候,直接从容器里面去解析。
依赖注入(DI)
依赖注入(DI):Dependency Injection的缩写。依赖注入是控制反转的一种实现方式,依赖注入的目的就是为了实现控制反转。
依赖注入是一种工具或手段,目的是帮助我们开发出松耦合、可维护的程序。
演示类
关于这个演示类,看接口文章
namespace ConsoleApp2{//交通工具接口interface IVehicle{void Run();}//武器接口interface IWeapon{void Fire();}//飞行接口interface IFly{void Fly();}//坦克接口,实现了交通工具的功能,还实现了武器的功能interface ITank:IVehicle,IWeapon{}//战斗机接口interface IFighter : IVehicle, IWeapon, IFly{}//汽车class Car : IVehicle{public void Run(){Console.WriteLine("汽车跑");}}//坦克class Tank : ITank{public void Fire(){Console.WriteLine("坦克开火");}public void Run(){Console.WriteLine("坦克跑");}}//战斗机class Fighter : IFighter{public void Fire(){Console.WriteLine("飞机开火");}public void Fly(){Console.WriteLine("飞机飞");}public void Run(){Console.WriteLine("飞机跑");}}//驾驶人class Person{//只要一个交通工具,不管是车,坦克还是飞机。IVehicle是这些的基接口private IVehicle vehicle;public Person(IVehicle vehicle){this.vehicle = vehicle;}public void Drive(){//我只需要跑的功能vehicle.Run();}public void Attack(){//如果要使用Fire,接口就不能是基接口IVehicle了,因为他没有,得是IWeapon//凡是继承了IWeapon接口的都能用}}}
安装框架
依赖注入 要用 依赖注入框架,框架很多,这里使用其中一个
NuGet搜索:DependencyInjection
引入名称空间
using Microsoft.Extensions.DependencyInjection;
创建依赖注入
创建依赖注入程序只用执行一次,但是要使用,sp一定要在一个公共的能访问都的地方
static void Main(string[] args){//创建一个服务的提供者容器var sc = new ServiceCollection();//往容器里面装东西,参数1:基接口类型,参数2:实现类类型,创建依赖关系sc.AddScoped(typeof(IWeapon), typeof(Tank));//注册,要使用依赖注入,sp一定是要能访问到地方var sp = sc.BuildServiceProvider();}
简单使用依赖注入
static void Main(string[] args){//创建一个服务的提供者容器var sc = new ServiceCollection();//往容器里面装东西,参数1:基接口类型,参数2:实现类类型sc.AddScoped(typeof(IWeapon), typeof(Tank));//注册var sp = sc.BuildServiceProvider();//----------------------------------------//使用依赖注入//从容器里面取出一个添加的对象,基接口类型,引用的是实现类的对象//这样就获取了一个tank对象,能只能tank对象的一切功能IWeapon tank = sp.GetService<IWeapon>();tank.Fire();}
使用依赖注入的好处
如果一个项目很多个文件里面创建了很多个 new Tank();对象。如果需求改变了,不要Tank了,全部改成Fighter。那只能去一个一个把new Tank();改成new Fighter();了。
用了依赖注入,反正接收的类型都是IWeapon,Tank和Fighter的基接口。直接改变ServiceCollection里面的参数后,凡是使用这个依赖注入的地方,都变成了,我改变的那个
static void Main(string[] args){var sc = new ServiceCollection();sc.AddScoped(typeof(IWeapon), typeof(Fighter));var sp = sc.BuildServiceProvider();//----------------------------------------//变量名我都懒得改,我只改了上面的Fighter,其他都没动。//整个代码中sp.GetService<IWeapon>()的都变成了战斗机IWeapon tank = sp.GetService<IWeapon>();tank.Fire();//飞机开火}
升级点的注入用法
同样的,只要我改变了创建依赖注入时 容器里的参数,凡是使用依赖注入的都跟着自动变。不用手动去改代码。多态的概念
static void Main(string[] args){var sc = new ServiceCollection();//两种方式都可以sc.AddScoped(typeof(IWeapon), typeof(Fighter));sc.AddScoped<IVehicle, Car>();//传入一个类//这个类实例化时需要传入一个IVehicle对象//sc.AddScoped<Person>();sc.AddScoped(typeof(Person));var sp = sc.BuildServiceProvider();//----------------------------------------//调用时,不需要手动添加实现类,因为系统会自动在容器里面找IVehicle接口,找到了,发现实现类是Car//所以给Person传入的就是Car对象,把Car注入进了Personvar p = sp.GetService<Person>();p.Drive();//车在跑}
服务的生命周期
就是Add后面的半截方法名
Transient:每次被请求都会生成一个新的实例 Scoped:一次web请求产生一个实例,web请求被处理完生命周期就截止了 Singleton:这个服务的实例一旦被创建,以后用这个服务的时候都会只用这一个实例,会一直存活到这个项目停止运行
