已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
当前回答
依赖注入是一种实践,它使解耦的组件与它们的一些依赖不可知,这遵循SOLID准则
依赖反转原则:一个人应该“依赖于抽象,而不是结核。
依赖注入的更好实现是Composition Root设计模式,因为它允许组件与依赖注入容器分离。
我再次推荐这篇关于作文根的伟大文章http://blog.ploeh.dk/2011/07/28/CompositionRoot/作者:Mark Seemann
本文的要点如下:
合成根是应用程序中的(最好)唯一位置其中模块被组合在一起。
...
只有应用程序应该具有合成根。图书馆和框架不应该。
...
DI容器只能从合成根引用。所有其他模块都不应引用容器。
Di Ninja(依赖注入框架)的文档是一个很好的例子,可以演示组合根和依赖注入的原理是如何工作的。https://github.com/di-ninja/di-ninja正如我所知,是javascript中唯一实现Composition Root设计模式的DiC。
其他回答
例如,我们有两类客户机和服务。客户端将使用服务
public class Service {
public void doSomeThingInService() {
// ...
}
}
无依赖注入
方式1)
public class Client {
public void doSomeThingInClient() {
Service service = new Service();
service.doSomeThingInService();
}
}
方式2)
public class Client {
Service service = new Service();
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
方式3)
public class Client {
Service service;
public Client() {
service = new Service();
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
1) 2)3)使用
Client client = new Client();
client.doSomeThingInService();
优势
易于理解的
缺点
难以测试客户端类当我们更改Service构造函数时,我们需要在所有位置更改代码createService对象
使用依赖注入
方式1)构造函数注入
public class Client {
Service service;
Client(Service service) {
this.service = service;
}
// Example Client has 2 dependency
// Client(Service service, IDatabas database) {
// this.service = service;
// this.database = database;
// }
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();
方式2)沉淀剂注入
public class Client {
Service service;
public void setService(Service service) {
this.service = service;
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();
方式3)接口注入
检查https://en.wikipedia.org/wiki/Dependency_injection
===
现在,这段代码已经遵循了依赖注入,测试客户端类更容易。然而,我们仍然多次使用新的Service(),并且在更改Service构造函数时效果不佳。为了防止这种情况,我们可以使用DI注射器1) 简单手动喷油器
public class Injector {
public static Service provideService(){
return new Service();
}
public static IDatabase provideDatatBase(){
return new SqliteDatabase();
}
public static ObjectA provideObjectA(){
return new ObjectA(provideService(...));
}
}
使用
Service service = Injector.provideService();
2) 使用库:适用于Android dagger2
优势
使测试更容易更改服务时,只需在Injector类中更改如果您使用使用构造函数注入,当您查看Client的构造函数时,您将看到Client类有多少依赖项
缺点
如果使用构造函数注入,则在创建客户端时创建服务对象,有时我们在客户端类中使用函数而不使用服务,因此创建的服务被浪费
依赖注入定义
https://en.wikipedia.org/wiki/Dependency_injection
依赖项是可以使用的对象(服务)注入是将依赖项(Service)传递给将使用它的依赖对象(Client)
让我们用Car和Engine类来尝试一个简单的例子,任何汽车都需要一个引擎,至少目前是这样。下面是没有依赖注入的代码的外观。
public class Car
{
public Car()
{
GasEngine engine = new GasEngine();
engine.Start();
}
}
public class GasEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
为了实例化Car类,我们将使用以下代码:
Car car = new Car();
这个代码的问题是我们与GasEngine紧密耦合,如果我们决定将其更改为ElectricityEngine,那么我们需要重写Car类。应用程序越大,我们必须添加和使用新型引擎的问题和麻烦就越多。
换句话说,这种方法是我们的高级Car类依赖于低级GasEngine类,这违反了SOLID的依赖反转原理(DIP)。DIP建议我们应该依赖抽象,而不是具体的类。因此,为了满足这一点,我们引入了IEngine接口并重写如下代码:
public interface IEngine
{
void Start();
}
public class GasEngine : IEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
public class ElectricityEngine : IEngine
{
public void Start()
{
Console.WriteLine("I am electrocar");
}
}
public class Car
{
private readonly IEngine _engine;
public Car(IEngine engine)
{
_engine = engine;
}
public void Run()
{
_engine.Start();
}
}
现在我们的Car类只依赖于IEngine接口,而不是引擎的特定实现。现在,唯一的诀窍是我们如何创建Car的实例,并给它一个实际的具体Engine类,比如GasEngine或ElectricityEngine。这就是依赖注入的作用。
Car gasCar = new Car(new GasEngine());
gasCar.Run();
Car electroCar = new Car(new ElectricityEngine());
electroCar.Run();
在这里,我们基本上将依赖项(Engine实例)注入(传递)到Car构造函数。因此,现在我们的类在对象及其依赖关系之间具有松散的耦合,我们可以在不更改Car类的情况下轻松添加新类型的引擎。
依赖注入的主要好处是类之间的耦合更加松散,因为它们没有硬编码的依赖关系。这遵循了上面提到的依赖反转原则。类不是引用特定的实现,而是请求抽象(通常是接口),这些抽象是在构造类时提供给它们的。
所以最终依赖注入只是一种技术实现对象及其依赖关系之间的松散耦合。而不是直接实例化类需要的依赖项为了执行其操作,向类提供依赖项(通常)通过构造函数注入。
此外,当我们有很多依赖项时,使用控制反转(IoC)容器是非常好的做法,我们可以告诉哪些接口应该映射到我们所有依赖项的具体实现,我们可以让它在构建对象时为我们解决这些依赖项。例如,我们可以在IoC容器的映射中指定IEngine依赖项应映射到GasEngine类,当我们向IoC容器请求Car类的实例时,它将自动构建Car类,并传入GasEngine依赖项。
更新:最近观看了Julie Lerman关于EF Core的课程,也喜欢她对DI的简短定义。
依赖注入是一种允许应用程序注入的模式对象,而无需强制类来负责这些对象。它允许您的代码更松散的耦合,并且实体框架核心插入到该框架中服务体系。
什么是依赖注入?
依赖注入(DI)意味着分离彼此依赖的对象。假设对象A依赖于对象B,因此想法是将这些对象彼此分离。我们不需要使用new关键字对对象进行硬编码,而是在运行时共享对对象的依赖关系,而不管编译时间如何。如果我们谈论
依赖注入在Spring中的工作原理:
我们不需要使用new关键字硬编码对象,而是在配置文件中定义bean依赖关系。弹簧容器将负责连接所有部件。
控制反转(IOC)
IOC是一个通用概念,可以用多种不同的方式表达,依赖注入是IOC的一个具体例子。
两种类型的依赖注入:
构造器注入沉淀剂注入
1.基于构造函数的依赖注入:
当容器调用具有多个参数的类构造函数时,就完成了基于构造函数的DI,每个参数表示对其他类的依赖。
public class Triangle {
private String type;
public String getType(){
return type;
}
public Triangle(String type){ //constructor injection
this.type=type;
}
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
<constructor-arg value="20"/>
</bean>
2.基于Setter的依赖注入:
基于Setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean上的Setter方法来实现的。
public class Triangle{
private String type;
public String getType(){
return type;
}
public void setType(String type){ //setter injection
this.type = type;
}
}
<!-- setter injection -->
<bean id="triangle" class="com.test.dependencyInjection.Triangle">
<property name="type" value="equivialteral"/>
注:对于强制依赖项使用构造函数参数,对于可选依赖项使用setter,这是一个很好的经验法则。注意,如果我们在setter上使用基于@Required的注释,则可以将setter作为必需的依赖项。
我将提出一个稍微不同的、简短而精确的依赖注入定义,侧重于主要目标,而不是技术手段(从这里开始):
依赖注入是创建静态、无状态的服务对象图,其中每个服务由其依赖关系。
我们在应用程序中创建的对象(无论我们是否使用Java、C#或其他面向对象语言)通常分为两类:无状态、静态和全局“服务对象”(模块),以及有状态、动态和本地“数据对象”。
模块图(服务对象图)通常在应用程序启动时创建。这可以使用容器(如Spring)完成,但也可以通过向对象构造函数传递参数来手动完成。这两种方法都有其优点和缺点,但在应用程序中使用DI肯定不需要框架。
一个要求是服务必须通过其依赖性进行参数化。这意味着什么完全取决于给定系统中采用的语言和方法。通常,这采用构造函数参数的形式,但使用setter也是一种选择。这也意味着(在调用服务方法时)对服务的用户隐藏服务的依赖关系。
何时使用?我会说,每当应用程序足够大时,将逻辑封装到单独的模块中,在模块之间使用依赖关系图,可以提高代码的可读性和可探索性。
我在松耦合方面发现了一个有趣的例子:
来源:了解依赖注入
任何应用程序都由许多对象组成,这些对象相互协作以执行一些有用的任务。传统上,每个对象都负责获取自己对与其协作的依赖对象(依赖关系)的引用。这导致了高度耦合的类和难以测试的代码。
例如,考虑Car对象。
汽车依靠轮子、发动机、燃料、电池等运转。传统上,我们定义此类依赖对象的品牌以及Car对象的定义。
无依赖注入(DI):
class Car{
private Wheel wh = new NepaliRubberWheel();
private Battery bt = new ExcideBattery();
//The rest
}
在这里,Car对象负责创建从属对象。
如果我们希望在初始NepaliRubberWheel()穿孔后更改其从属对象的类型(例如Wheel),该怎么办?我们需要重新创建Car对象及其新的依赖项,例如ChineseRubberWheel(),但只有Car制造商才能做到这一点。
那么依赖注入为我们做了什么。。。?
当使用依赖注入时,对象在运行时而不是编译时(汽车制造时)被赋予依赖性。因此,我们现在可以随时更改轮子。在这里,相关性(轮子)可以在运行时注入Car。
使用依赖注入后:
这里,我们在运行时注入依赖项(Wheel和Battery)。因此有了这个词:依赖注入。我们通常依赖于Spring、Guice、Weld等DI框架来创建依赖关系并在需要时注入。
class Car{
private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtime
private Battery bt; // Inject an Instance of Battery (dependency of car) at runtime
Car(Wheel wh,Battery bt) {
this.wh = wh;
this.bt = bt;
}
//Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
其优点是:
分离对象的创建(换句话说,将使用与对象的创建分开)能够替换依赖项(例如:车轮、电池),而不改变使用它的类(汽车)促进“代码到接口而不是实现”原则在测试期间创建和使用模拟依赖关系的能力(如果我们想在测试期间使用模拟轮而不是真实实例,我们可以创建模拟轮对象并让DI框架注入Car)