已经发布了几个问题,其中包括有关依赖注入的特定问题,例如何时使用它以及使用什么框架。然而,
什么是依赖项注入?何时/为什么/不应该使用它?
已经发布了几个问题,其中包括有关依赖注入的特定问题,例如何时使用它以及使用什么框架。然而,
什么是依赖项注入?何时/为什么/不应该使用它?
Answers:
依赖注入将依赖传递给其他对象或框架(依赖注入器)。
依赖注入使测试更加容易。注入可以通过构造函数完成。
SomeClass()
其构造函数如下:
public SomeClass() {
myObject = Factory.getObject();
}
问题:如果myObject
涉及复杂的任务,例如磁盘访问或网络访问,则很难对其进行单元测试SomeClass()
。程序员必须进行模拟,myObject
并可能拦截工厂调用。
替代解决方案:
myObject
作为参数传递给构造函数public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject
可以直接通过,这使测试更加容易。
没有依赖注入的情况下,很难在单元测试中隔离组件。
2013年,当我撰写此答案时,这是Google Testing Blog的一个主要主题。这对我来说仍然是最大的优势,因为程序员在运行时设计中并不总是需要额外的灵活性(例如,对于服务定位器或类似的模式)。程序员经常需要在测试期间隔离类。
到目前为止,我发现的最佳定义是James Shore定义的:
“依赖注入”是5美分概念的25美元术语。依赖注入意味着给对象一个实例变量。[...]。
目前由Martin Fowler的一篇文章,可能证明是有用的,太。
依赖注入基本上是提供对象需要的对象(其依赖),而不是让对象自己构造它们。这是一种非常有用的测试技术,因为它允许对依赖项进行模拟或存根。
可以通过多种方式将依赖项注入到对象中(例如构造函数注入或setter注入)。甚至可以使用专门的依赖项注入框架(例如Spring)来做到这一点,但是肯定不是必需的。您不需要那些框架具有依赖项注入。显式实例化和传递对象(依赖项)与框架注入一样好。
我从松耦合的角度发现了这个有趣的例子:
任何应用程序都由许多相互协作以执行某些有用内容的对象组成。传统上,每个对象都负责获得自己对其与之协作的依赖对象(依赖关系)的引用。这导致了高度耦合的类和难以测试的代码。
例如,考虑一个Car
对象。
A Car
取决于车轮,发动机,燃料,电池等的运行。传统上,我们会定义此类从属对象的品牌以及对象的定义Car
。
没有依赖注入(DI):
class Car{
private Wheel wh = new NepaliRubberWheel();
private Battery bt = new ExcideBattery();
//The rest
}
在此,Car
对象负责创建从属对象。
如果我们想Wheel
在初始NepaliRubberWheel()
刺孔之后更改其从属对象的类型(例如),该怎么办?我们需要使用其新依赖关系say重新创建Car对象ChineseRubberWheel()
,但是只有Car
制造商才能做到这一点。
那Dependency Injection
对我们有什么作用呢?
使用依赖注入时,在运行时而不是编译时(汽车制造时间)为对象提供它们的依赖。这样我们现在就可以随时更改Wheel
。在这里,dependency
(wheel
)可以Car
在运行时注入。
使用依赖项注入后:
在这里,我们注入的依赖性在运行时(轮和电池)。因此,术语:依赖注入。
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;
}
}
资料来源:了解依赖注入
new
一累?我不。我要做的就是从他们那里购买(通过param注入),安装并哇哇!因此,回到编程,说C#项目需要使用现有的库/类,有两种运行/调试的方法,1-添加对此的整个项目的引用
new
传递给它,将选项2传递给它作为参数。可能不准确,但简单愚蠢易懂。
依赖注入是一种实践,其中设计对象的方式是使它们从其他代码段接收对象的实例,而不是在内部构造它们。这意味着可以在不更改代码的情况下替换任何实现该对象所需接口的对象,从而简化了测试并改善了去耦。
例如,考虑以下情况:
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
在这个例子中,执行PersonService::addManager
和PersonService::removeManager
需要的一个实例GroupMembershipService
,以完成其工作。没有依赖注入,传统的方式是GroupMembershipService
在构造函数中实例化一个new PersonService
并在两个函数中使用该实例属性。但是,如果的构造函数GroupMembershipService
需要执行多项操作,或者更糟糕的是,则需要在上调用一些初始化“设置程序”,GroupMembershipService
代码会快速增长,PersonService
现在不仅取决于GroupMembershipService
,还取决于其他所有内容GroupMembershipService
取决于。此外,与的链接GroupMembershipService
已硬编码到中,PersonService
这意味着您无法“虚拟”GroupMembershipService
出于测试目的,或在应用程序的不同部分中使用策略模式。
依赖注入,而不是实例化GroupMembershipService
的内PersonService
,你要么把它传递到PersonService
构造函数,否则添加属性(getter和setter)来设置它的本地实例。这意味着您PersonService
不再需要担心如何创建GroupMembershipService
,而只需接受给定的,然后与它们一起工作。这也意味着可以将任何作为的子类GroupMembershipService
或实现GroupMembershipService
接口的东西“注入”到中PersonService
,而PersonService
无需知道更改。
公认的答案是一个很好的答案-但我想补充一点,DI非常类似于经典避免代码中的硬编码常量。
当您使用诸如数据库名称之类的常量时,您需要将其从代码内部快速移至某些配置文件,并将包含该值的变量传递到需要它的地方。这样做的原因是这些常量通常比其余代码更频繁地更改。例如,如果您想在测试数据库中测试代码。
在面向对象的编程领域,DI与此类似。那里的值而不是常量文字是整个对象-但是将创建它们的代码从类代码中移出的原因是相似的-与使用它们的代码相比,对象更改的频率更高。需要进行此类更改的一个重要案例是测试。
让我们尝试一个有关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类。
依赖注入的主要好处是,类之间的耦合更为松散,因为它们没有硬编码的依赖关系。这遵循了上面提到的依赖倒置原则。类不引用特定的实现,而是请求构造类时提供给它们的抽象(通常是interface)。
因此,最终依赖注入只是一种用于实现对象及其依赖之间的松散耦合的技术。与其直接实例化类执行其动作所需的依赖关系,不如(通常)通过构造函数注入将依赖关系提供给该类。
同样,当我们有很多依赖项时,最好使用Inversion of Control(IoC)容器,该容器可以告诉我们应该将哪些接口映射到所有依赖项的具体实现上,并且可以在构造时为我们解决这些依赖项我们的对象。例如,我们可以在IoC容器的映射中指定将IEngine依赖项映射到GasEngine类,并且当我们向IoC容器询问Car类的实例时,它将自动构造具有GasEngine依赖项的Car类通过了。
更新:最近观看了朱莉·勒曼(Julie Lerman)的有关EF Core的课程,并且也喜欢她关于DI的简短定义。
依赖注入是一种模式,允许您的应用程序将对象动态注入到需要它们的类中,而不必强制那些类对那些对象负责。它使您的代码可以更松散地耦合在一起,并且Entity Framework Core可以插入该相同的服务系统。
假设您想去钓鱼:
如果没有依赖项注入,则您需要自己做好一切。您需要找到一条船,购买一根钓鱼竿,寻找诱饵等。当然有可能,但是这给您带来了很多责任。用软件术语,这意味着您必须对所有这些内容执行查找。
使用依赖注入,其他人可以完成所有准备工作,并为您提供所需的设备。您将收到(“被注射”)船,钓鱼竿和诱饵-随时可用。
这是我见过的关于依赖注入和依赖注入容器的最简单的解释:
依赖注入和依赖注入容器是不同的东西:
您不需要容器即可进行依赖项注入。但是,容器可以帮助您。
“依赖注入”不仅仅意味着使用参数化的构造函数和公共设置器吗?
没有依赖项注入的构造函数:
public class Example { private DatabaseThingie myDatabase; public Example() { myDatabase = new DatabaseThingie(); } public void doStuff() { ... myDatabase.getData(); ... } }
具有依赖项注入的构造函数:
public class Example { private DatabaseThingie myDatabase; public Example(DatabaseThingie useThisDatabaseInstead) { myDatabase = useThisDatabaseInstead; } public void doStuff() { ... myDatabase.getData(); ... } }
new DatabaseThingie()
不生成有效的myDatabase实例时。
使“依赖注入”的概念易于理解。让我们以开关按钮为例,切换(打开/关闭)灯泡。
交换机需要事先知道我连接到哪个灯泡(硬编码依赖性)。所以,
开关-> PermanentBulb //开关直接连接到永久灯泡,无法轻松测试
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
开关只知道我需要打开/关闭任何传递给我的灯泡。所以,
开关-> Bulb1 OR Bulb2 OR NightBulb(注入依赖项)
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}
修改开关和灯泡的James示例:
public class SwitchTest {
TestToggleBulb() {
MockBulb mockbulb = new MockBulb();
// MockBulb is a subclass of Bulb, so we can
// "inject" it here:
Switch switch = new Switch(mockBulb);
switch.ToggleBulb();
mockBulb.AssertToggleWasCalled();
}
}
public class Switch {
private Bulb myBulb;
public Switch() {
myBulb = new Bulb();
}
public Switch(Bulb useThisBulbInstead) {
myBulb = useThisBulbInstead;
}
public void ToggleBulb() {
...
myBulb.Toggle();
...
}
}`
什么是依赖注入(DI)?
就像其他人所说的,依赖注入(DI)消除了我们感兴趣的类(消费者类)所依赖的其他对象实例(在UML意义上)直接创建和寿命管理的责任。这些实例通常作为构造函数参数或通过属性设置器传递给我们的消费者类(对依赖对象实例化和传递给消费者类的管理通常由控制反转(IoC)容器执行,但这是另一个主题) 。
DI,DIP和SOLID
具体而言,在罗伯特·Ç马丁的范式面向对象设计的SOLID原则,DI
为可能的实现方式之一依赖倒置原则(DIP) 。所述DIP是D
所述的SOLID
咒语 -其他DIP实现包括了服务定位器,和插件图案。
拨码的目标是类之间对紧解耦,混凝土的依赖关系,而代之以由一个抽象,其可以通过一个来实现的装置松开联接interface
,abstract class
或者pure virtual class
,取决于所使用的语言和方法。
如果没有DIP,我们的代码(我称为“消费类”)直接与具体的依赖项耦合,并且通常还承担着知道如何获取和管理该依赖项实例的责任,即从概念上讲:
"I need to create/use a Foo and invoke method `GetBar()`"
而在应用DIP之后,该要求被放宽了,并且Foo
消除了获取和管理依赖项寿命的问题:
"I need to invoke something which offers `GetBar()`"
为什么要使用DIP(和DI)?
以这种方式将类之间的依赖关系解耦允许将这些依赖关系类轻松替换为也满足抽象前提的其他实现(例如,可以通过同一接口的另一实现来切换依赖关系)。此外,正如其他人所提到的那样,通过DIP解耦类的最常见原因可能是允许对消费类进行隔离测试,因为现在可以对这些相同的依赖项进行存根和/或模拟了。
DI的一个结果是,依赖项实例的生命周期管理不再受使用方类的控制,因为依赖项对象现在已通过构造函数或setter注入传递到使用方类中。
可以用不同的方式查看:
Create
根据需要通过工厂上的实例来获取实例,并在完成后处置这些实例。什么时候使用DI?
MyDepClass
线程安全吗?如果我们将其设为单例并将同一个实例注入所有使用者,该怎么办?)例
这是一个简单的C#实现。给定以下消费类:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
虽然看似无害的,它有两个static
其他两个类,依赖性System.DateTime
和System.Console
,这不仅限制了记录输出选项(记录到控制台将毫无价值,如果没有人看),但糟糕的是,它是很难自动测试给出的依赖一个不确定的系统时钟。
但是,我们可以DIP
通过将时间戳记作为依赖项的关注抽象出来,并MyLogger
仅耦合到简单的接口来应用于此类:
public interface IClock
{
DateTime Now { get; }
}
我们还可以放宽对Console
抽象的依赖,例如TextWriter
。依赖项注入通常实现为constructor
注入(将依赖项的抽象作为消费类的构造函数的参数Setter Injection
传递给依赖项)或(通过setXyz()
设置器或带有{set;}
定义的.Net属性传递依赖项)。最好使用构造函数注入,因为这样可以确保在构造后类将处于正确的状态,并允许将内部依赖项字段标记为readonly
(C#)或final
(Java)。因此,在上面的示例中使用构造函数注入,这使我们拥有:
public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
(Clock
需要提供具体的内容,当然可以将其还原为DateTime.Now
,并且两个依赖关系需要由IoC容器通过构造函数注入来提供)
可以构建一个自动化的单元测试,这可以肯定地证明我们的记录器工作正常,因为现在我们可以控制依赖项(时间),并且可以监视书面输出:
[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
下一步
依赖项注入始终与“ 反转控制容器”(IoC)相关联,以注入(提供)具体的依赖项实例并管理生命周期实例。在配置/引导过程中,IoC
容器允许定义以下内容:
IBar
,返回ConcreteBar
实例”)IDisposable
,并将根据Disposing
已配置的寿命管理来承担依赖项的责任。通常,一旦IoC容器已配置/自举,它们将在后台无缝运行,从而使编码人员可以专注于手头的代码,而不必担心依赖关系。
DI友好代码的关键是避免类的静态耦合,而不是使用new()创建依赖项
如上面的示例所示,依赖关系的分离确实需要一些设计工作,并且对于开发人员而言,需要进行范式转换,以打破new
直接使用依赖关系的习惯,而是信任容器来管理依赖关系。
但是好处很多,尤其是能够全面测试您感兴趣的课程。
注意:new ..()
POCO / POJO /序列化DTO /实体图/匿名JSON投影等的创建/映射/投影(通过)-即“仅数据”类或记录-从方法使用或返回的方法不视为依赖项(在UML意义上的),并且不受DI约束。利用new
项目,这些仅仅是罚款。
依赖注入(DI)的全部目的是保持应用程序源代码的清洁和稳定:
实际上,每种设计模式都将关注点分开,以使将来的更改影响最小文件。
DI的特定域是依赖项配置和初始化的委派。
如果您偶尔在Java之外工作,请回想一下source
许多脚本语言(Shell,Tcl等,甚至import
用于此目的的Python)经常使用的语言。
考虑简单的dependent.sh
脚本:
#!/bin/sh
# Dependent
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
该脚本是依赖的:它不会自行成功执行(archive_files
未定义)。
您archive_files
在archive_files_zip.sh
实现脚本中定义(zip
在这种情况下使用):
#!/bin/sh
# Dependency
function archive_files {
zip files.zip "$@"
}
而不是source
直接在相关脚本中添加实现脚本,而是使用injector.sh
“容器”包装两个“组件”:
#!/bin/sh
# Injector
source ./archive_files_zip.sh
source ./dependent.sh
该archive_files
依赖刚刚被注入到相关的脚本。
您可能已经注入了archive_files
使用tar
或实现的依赖项xz
。
如果dependent.sh
脚本直接使用依赖项,则该方法将称为依赖项查找(与依赖项注入相反):
#!/bin/sh
# Dependent
# dependency look-up
source ./archive_files_zip.sh
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
现在的问题是,依赖的“组件”必须自己执行初始化。
“组件”的源代码既不是干净的也不是稳定的,因为对依赖关系的初始化的每次更改都需要“组件”的源代码文件的新发行版。
DI没有像Java框架那样受到广泛的重视和普及。
但这是将关注点分开的通用方法:
仅通过依赖关系查找使用配置无济于事,因为每个依赖关系(例如,新的身份验证类型)以及支持的依赖关系类型的数量(例如,新的数据库类型)可能会更改配置参数的数量。
以上所有答案都是好的,我的目的是用一种简单的方式解释这个概念,以便没有编程知识的人也可以理解这个概念。
依赖注入是一种设计模式,可以帮助我们以更简单的方式创建复杂的系统。
我们可以在日常生活中看到这种模式的广泛应用。一些示例是录音机,VCD,CD驱动器等。
上图是20世纪中叶的卷到卷便携式录音机的图像。来源。
录音机的主要目的是录制或播放声音。
在设计系统时,需要使用拨盘来录制或播放声音或音乐。设计此系统有两种可能性
如果使用第一个,则需要打开机器以更换卷轴。如果我们选择第二个,那就是挂在转盘上,那么通过更换转盘,您将获得播放任何音乐的额外好处。并将功能简化为仅播放卷轴中的任何内容。
就像明智的依赖注入一样,是将依赖关系外部化以仅关注组件的特定功能的过程,以便独立的组件可以耦合在一起以形成一个复杂的系统。
我们通过使用依赖项注入获得了主要好处。
如今,这些概念构成了编程界众所周知的框架的基础。Spring Angular等是基于此概念构建的著名软件框架
依赖注入是一种用于创建其他对象所依赖的对象实例的模式,这种模式在编译时不知道将使用哪个类来提供该功能或简单地将属性注入对象的方式称为依赖注入。
依赖注入示例
以前我们是这样写的
Public MyClass{
DependentClass dependentObject
/*
At somewhere in our code we need to instantiate
the object with new operator inorder to use it or perform some method.
*/
dependentObject= new DependentClass();
dependentObject.someMethod();
}
使用依赖注入,依赖注入器将为我们完成实例化
Public MyClass{
/* Dependency injector will instantiate object*/
DependentClass dependentObject
/*
At somewhere in our code we perform some method.
The process of instantiation will be handled by the dependency injector
*/
dependentObject.someMethod();
}
您也可以阅读
依赖注入(DI)意味着将相互依赖的对象分离。假设对象A依赖于对象B,所以想法是将这些对象彼此分离。尽管编译时,我们不需要使用new关键字对对象进行硬编码,而在运行时共享对对象的依赖关系。如果我们谈论
我们不需要使用new关键字对对象进行硬编码,而是在配置文件中定义Bean依赖项。弹簧容器将负责所有连接。
IOC是一个通用概念,可以用许多不同的方式表示,并且依赖注入是IOC的一个具体示例。
当容器调用带有多个参数的类构造函数时,将完成基于构造函数的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>
通过使用无参数构造函数或无参数静态工厂方法实例化您的bean之后,容器通过在bean上调用setter方法来完成基于setter的DI。
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"/>
注意:将构造函数参数用于强制性依赖项,将设置器用于可选的依赖性,这是一个很好的经验法则。请注意,如果我们在设置器上使用基于注释的@@ Required注释,则可以将其用作必需的依赖项。
我能想到的最好的比喻是手术室中的外科医生及其助手,外科医生是主要人物,而他的助手会在需要时提供各种手术部件,以便外科医生可以专注于一个他最擅长的事情(手术)。没有助手,外科医生就必须在每次需要时自己准备零件。
简而言之,DI是一种通过向组件提供依赖来消除组件上常见的附加责任(负担)以提取依赖组件的技术。
DI使您更接近单一责任(SR)原则,例如surgeon who can concentrate on surgery
。
何时使用DI:我建议几乎在所有生产项目(小型/大型)中都使用DI,尤其是在瞬息万变的商业环境中:)
原因:因为您希望代码易于测试,可模拟等,以便您可以快速测试更改并将其推向市场。此外,为什么当您有很多很棒的免费工具/框架可以为您提供更多控制权的代码库之旅中提供支持时,您为什么不这样做呢?
例如,我们有2个类Client
和Service
。Client
将使用Service
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();
优点
缺点
Client
上课Service
构造函数时,需要在所有位置更改代码以创建Service
对象方式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) Setter注入
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
===
现在,此代码已经在后面Dependency Injection
,对于测试Client
类来说更容易。
但是,我们仍然会new Service()
花费很多时间,并且在更改Service
构造函数时效果不好。为了防止这种情况,我们可以使用DI注射器,例如
1)简单手册Injector
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
优点
Service
,只需在Injector类中进行更改Constructor Injection
,则在查看的构造函数时Client
,您会看到Client
该类有多少依赖关系缺点
Constructor Injection
,Service
则在Client
创建对象时创建该对象,有时我们在Client
类中使用函数而不使用它,Service
因此Service
浪费了创建的时间https://zh.wikipedia.org/wiki/Dependency_injection
依赖项是可以使用的对象(
Service
)
注入是将依赖项(Service
)传递给Client
将使用它的依赖对象()
这意味着对象应该只具有完成其工作所需的依赖关系,而依赖关系应该很少。此外,在可能的情况下,对象的依赖项应位于接口上,而不应位于“具体”对象上。(具体对象是使用关键字new创建的任何对象。)松散耦合可提高重用性,更易于维护,并允许您轻松提供“模拟”对象来代替昂贵的服务。
“依赖注入”(DI)也称为“控制反转”(IoC),可以用作鼓励这种松散耦合的技术。
实施DI的主要方法有两种:
这是将对象依赖项传递给其构造函数的技术。
请注意,构造函数接受接口,而不接受具体对象。另外,请注意,如果orderDao参数为null,则会引发异常。这强调了接收有效依赖关系的重要性。在我看来,构造函数注入是赋予对象依赖项的首选机制。开发人员很清楚在调用对象时需要将哪些依赖项赋予“ Person”对象以正确执行。
但是,请考虑以下示例……假设您有一个类,其中包含十个没有依赖关系的方法,但是您要添加一个确实依赖于IDAO的新方法。您可以将构造函数更改为使用“构造函数注入”,但这可能会迫使您更改所有位置的所有构造函数调用。另外,您可以只添加一个带有依赖项的新构造函数,但是开发人员如何轻松知道何时使用一个构造函数而不是另一个。最后,如果创建的依赖项非常昂贵,那么当它很少使用时,为什么还要创建它并将其传递给构造函数呢?“注浆注入”是另一种可以在这种情况下使用的DI技术。
Setter Injection不会强制将依赖项传递给构造函数。而是将依赖项设置为需要的对象公开的公共属性。如前所述,这样做的主要动机包括:
以下是上述代码的示例:
public class Person {
public Person() {}
public IDAO Address {
set { addressdao = value; }
get {
if (addressdao == null)
throw new MemberAccessException("addressdao" +
" has not been initialized");
return addressdao;
}
}
public Address GetAddress() {
// ... code that uses the addressdao object
// to fetch address details from the datasource ...
}
// Should not be called directly;
// use the public property instead
private IDAO addressdao;
我想既然每个人都为DI写作,所以让我问几个问题。
这基于@Adam N发布的答案。
为什么PersonService不再需要担心GroupMembershipService?您刚刚提到GroupMembership有多个依赖项(对象/属性)。如果PService中需要GMService,则将其作为属性。无论是否注入,都可以模拟出来。我唯一希望注入的是GMService是否具有更特定的子类,直到运行时您才知道。然后,您想注入子类。或者,如果您想将其用作单例或原型。坦白说,配置文件对所有内容进行了硬编码,直到它在编译期间将要注入的类型(接口)的子类为止。
编辑
DI通过消除确定依赖性方向和编写任何胶合代码的任何需求来增加内聚力。
假。依赖关系的方向是XML形式或作为注释,您的依赖关系被编写为XML代码和注释。XML和注释是源代码。
DI通过使您的所有组件模块化(即可更换)并具有相互定义的接口来减少耦合。
假。您不需要DI框架即可基于接口构建模块化代码。
关于可替换:使用非常简单的.properties存档和Class.forName,您可以定义可以更改的类。如果可以更改代码的任何类,则Java不适合您,请使用脚本语言。顺便说一句:注释必须重新编译才能更改。
在我看来,DI框架的唯一原因是:减少锅炉板。有了完善的工厂系统,您可以像首选的DI框架一样执行相同的操作,并且可控性强,可预测性强,DI框架可以减少代码(XML和注释也是源代码)。问题在于,在非常简单的情况下(每个类一个实例,以及类似的实例),减少样板只是真实的,有时在现实世界中,选择合适的服务对象并不像将类映射到单例对象那样容易。
流行的答案无济于事,因为它们以一种无用的方式定义了依赖注入。让我们同意,“依赖”是指对象X需要的一些预先存在的其他对象。但是当我们说时,我们并不是说我们在做“依赖注入”
$foo = Foo->new($bar);
我们只是将传递参数称为构造函数。自从构造函数被发明以来,我们就一直定期这样做。
“依赖注入”被认为是“控制反转”的一种,这意味着某些逻辑会从调用者中取出。调用者传递参数时不是这种情况,因此,如果参数是DI,则DI不会暗示控制权的倒置。
DI表示在调用方和构造程序之间存在一个中间层,用于管理依赖项。Makefile是依赖项注入的一个简单示例。“调用者”是在命令行上键入“ make bar”的人,而“构造函数”是编译器。Makefile指定bar取决于foo,并且它执行
gcc -c foo.cpp; gcc -c bar.cpp
在做一个之前
gcc foo.o bar.o -o bar
输入“ make bar”的人不需要知道bar取决于foo。依赖项被注入“ make bar”和gcc之间。
中间级别的主要目的不仅是将依赖项传递给构造函数,而且还列出了所有依赖项。 一个位置,并从编码器中隐藏它们(而不是让编码器提供它们)。
通常,中间级别为构造的对象提供工厂,工厂必须提供每个请求的对象类型必须满足的角色。那是因为通过具有隐藏构造细节的中间层,您已经招致了工厂施加的抽象损失,因此您最好使用工厂。
我知道已经有很多答案了,但是我发现这很有帮助:http : //tutorials.jenkov.com/dependency-injection/index.html
public class MyDao {
protected DataSource dataSource = new DataSourceImpl(
"driver", "url", "user", "password");
//data access methods...
public Person readPerson(int primaryKey) {...}
}
public class MyDao {
protected DataSource dataSource = null;
public MyDao(String driver, String url, String user, String password) {
this.dataSource = new DataSourceImpl(driver, url, user, password);
}
//data access methods...
public Person readPerson(int primaryKey) {...}
}
请注意,DataSourceImpl
实例化如何移动到构造函数中。构造函数采用四个参数,它们是所需的四个值DataSourceImpl
。尽管MyDao
该类仍依赖于这四个值,但它本身已不再满足这些依赖关系。它们由创建MyDao
实例的任何类提供。
依赖注入是一种解决方案,它通常可以称为“依赖关系混淆”要求。依赖混淆是一种从“明显”性质中消除对需要它的类提供依赖的过程的方法,因此以某种方式混淆了对所述类的所述依赖的提供。这不一定是一件坏事。实际上,通过混淆向类提供依赖项的方式,然后类外部的某些事物将负责创建依赖项,这意味着在各种情况下,可以将依赖项的不同实现提供给类,而无需进行任何更改上课。这非常适合在生产和测试模式之间进行切换(例如,使用“模拟”服务依赖项)。
不幸的是,不幸的是,有些人认为您需要一个专门的框架来进行依赖关系模糊处理,并且如果您选择不使用特定的框架来进行编程,那么您在某种程度上就是一个“较少”的程序员。许多人认为,另一个令人极为不安的神话是,依赖注入是实现依赖混淆的唯一方法。从历史上可以证明,这显然是100%错误的,但是您将难以令人信服某些人,对于依赖项混淆要求,可以使用依赖项注入替代方法。
程序员已经了解依赖混淆的要求已有多年了,并且在构思依赖注入之前和之后,许多替代解决方案都在不断发展。有Factory模式,但也有许多使用ThreadLocal的选项,其中不需要注入到特定实例-依赖关系被有效地注入到线程中,这具有使对象可用(通过便捷的静态getter方法)可用的好处。任何需要它的类,而不必在需要它的类中添加注释,并设置复杂的XML“胶水”来实现它。当您的依赖关系是持久性所必需的(JPA / JDO或其他)时,它使您可以更轻松地实现“透明持久性”,并且领域模型和业务模型类完全由POJO组成(即,没有特定于框架/未锁定在注释中)。
摘自《基础扎实的Java开发人员:Java 7和多语言编程的重要技术》
DI是IoC的一种特殊形式,通过它可以发现依赖项的过程不受当前执行代码的直接控制。
在进行技术描述之前,首先用一个真实的例子将其可视化,因为您会发现很多技术知识来学习依赖关系注入,但是像我这样的人最多只能了解它的核心概念。
在第一张图片中,假设您有一家团结很多的汽车厂。汽车实际上是安装在装配单元中的,但是它需要发动机,座椅和车轮。因此,组装单元取决于所有这些单元,它们是工厂的依赖。
您可能会觉得现在很难在这个工厂中维护所有任务,因为除了主要任务(在组装单元中组装汽车)之外,您还必须专注于其他单元。现在它的维护成本非常高,工厂厂房巨大,因此您需要多付一些租金。
现在,看第二张图片。如果您找到一些供应商公司,这些公司可以为您提供比您自己生产的价格便宜的车轮,座椅和发动机,那么您现在不需要在工厂生产它们。您现在可以租一间较小的建筑物,仅用于组装单位这将减少维护工作并减少额外的租赁成本。现在,您也可以只专注于主要任务(汽车装配)。
现在我们可以说,组装汽车的所有依赖关系都是从提供商那里注入的。这是现实生活中的依赖注入(DI)的一个示例。
现在用专业术语来说,依赖注入是一种技术,通过这种技术,一个对象(或静态方法)提供了另一个对象的依赖关系。因此,将创建对象的任务转移给其他人并直接使用依赖项的过程称为依赖项注入。
摘自Book Apress.Spring.Persistence.Hibernate.Oct.2010
依赖项注入的目的是将解析外部软件组件的工作与应用程序业务逻辑分离开来。如果没有依赖项注入,组件代码如何访问组件访问所需服务的详细信息就会变得混乱。这不仅增加了潜在的错误,增加了代码膨胀,而且增加了维护复杂性;它将组件更紧密地耦合在一起,从而在重构或测试时很难修改依赖关系。
依赖注入(DI)是设计模式中的一种,它使用OOP的基本功能-一个对象与另一个对象之间的关系。继承继承了一个对象以执行更复杂和特定的另一个对象时,关系或关联只是使用属性从一个对象创建指向另一个对象的指针。DI的功能与接口和隐藏代码一样,与OOP的其他功能结合在一起。假设我们在图书馆中有一个客户(订户),为简单起见,该客户只能借用一本书。
书的界面:
package com.deepam.hidden;
public interface BookInterface {
public BookInterface setHeight(int height);
public BookInterface setPages(int pages);
public int getHeight();
public int getPages();
public String toString();
}
接下来,我们可以有很多书籍。一种类型是小说:
package com.deepam.hidden;
public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages
/** constructor */
public FictionBook() {
// TODO Auto-generated constructor stub
}
@Override
public FictionBook setHeight(int height) {
this.height = height;
return this;
}
@Override
public FictionBook setPages(int pages) {
this.pages = pages;
return this;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return height;
}
@Override
public int getPages() {
// TODO Auto-generated method stub
return pages;
}
@Override
public String toString(){
return ("height: " + height + ", " + "pages: " + pages);
}
}
现在,订户可以关联到该书:
package com.deepam.hidden;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Subscriber {
BookInterface book;
/** constructor*/
public Subscriber() {
// TODO Auto-generated constructor stub
}
// injection I
public void setBook(BookInterface book) {
this.book = book;
}
// injection II
public BookInterface setBook(String bookName) {
try {
Class<?> cl = Class.forName(bookName);
Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
BookInterface book = (BookInterface) constructor.newInstance();
//book = (BookInterface) Class.forName(bookName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return book;
}
public BookInterface getBook() {
return book;
}
public static void main(String[] args) {
}
}
这三个类都可以隐藏,因为它是自己实现的。现在我们可以将此代码用于DI了:
package com.deepam.implement;
import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;
public class CallHiddenImplBook {
public CallHiddenImplBook() {
// TODO Auto-generated constructor stub
}
public void doIt() {
Subscriber ab = new Subscriber();
// injection I
FictionBook bookI = new FictionBook();
bookI.setHeight(30); // cm
bookI.setPages(250);
ab.setBook(bookI); // inject
System.out.println("injection I " + ab.getBook().toString());
// injection II
FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
System.out.println("injection II " + ab.getBook().toString());
}
public static void main(String[] args) {
CallHiddenImplBook kh = new CallHiddenImplBook();
kh.doIt();
}
}
有许多不同的方法来使用依赖项注入。可以将它与Singleton等结合使用,但是从根本上讲,它仅是通过在另一个对象内部创建对象类型的属性来实现的关联。有用性仅在功能方面,我们应该一遍又一遍地编写的代码始终是为我们准备和完成的。这就是为什么DI与控制反转(IoC)如此紧密地绑定的原因,这意味着我们的程序将控制另一个正在运行的模块,该模块将Bean注入到我们的代码中。(每个可以注入的对象都可以签名或视为Bean。)例如,在Spring中,它是通过创建和初始化来完成的 ApplicationContext容器,为我们完成这项工作。我们只需在代码中创建Context并调用初始化bean。在那一刻,注射已自动完成。
从Pablo Deeleman的书Christopher Noring的“ Learning Angular-Second Edition”:
“随着我们的应用发展和演变,我们的每一个代码实体将在内部需要其他对象的实例,这是更好地称为依赖该软件工程的世界行动通过这样的依赖于相关的客户端被称为注射,而且还需要另一个代码实体命名的参与喷油器,该喷油器将承担责任实例化和自举所需的依赖因此,从成功将其注入客户端以来,它们就可以使用了。这是非常重要的,因为客户端对如何实例化自己的依赖关系一无所知,并且仅知道它们为使用它们而实现的接口。”
来自:安东·莫伊谢夫(Anton Moiseev)。本书“使用Typescript进行角度开发,第二版”:
“简而言之,DI帮助您以松散耦合的方式编写代码,并使您的代码更具可测试性和可重用性。”
简而言之,依赖注入(DI)是消除不同对象之间的依赖或紧密耦合的方法。依赖注入为每个对象赋予了凝聚力。
DI是Spring的IOC负责人的实现,说“不要打电话给我们,我们会打电话给您”。使用依赖注入,程序员不需要使用new关键字创建对象。
对象一旦被加载到Spring容器中,然后就可以通过使用getBean(String beanName)方法从Spring容器中获取这些对象来重用它们。
依赖注入是与Spring Framework相关的概念的核心。尽管创建任何项目spring的框架都可能起着至关重要的作用,但在这里,依赖注入成为了投手。
实际上,假设在Java中创建了两个不同的类,分别为A类和B类,并且想要在B类中使用的功能都想在A类中使用,那么那时可以使用依赖注入。您可以在其中创建另一个类的对象的方式相同,也可以将整个类注入另一个类以使其可访问。通过这种方式,可以克服依赖性。
依赖注射仅是将两个类别粘合在一起,并同时将它们分开。