英文:
What are the advantages of a dependency injection framework over classical dependency injection?
问题
我正在努力理解在 Jakarta EE 上下文中使用依赖注入框架(在我这个案例中是 CDI)的好处。
我想我理解了依赖注入的标准用例的要点 - 将实例变量的具体实例化“分离”出来通常是有益的,所以
public class Car {
private Tyre tyre;
public Car() {
tyre = new Tyre();
}
}
变成了
public class Car {
private Tyre tyre;
public Car(Tyre tyre) {
this.tyre = tyre;
}
}
这样做的好处是现在我们可以插入任何喜欢的轮胎,这在单元测试中很方便,例如,因为 Tyre 可能很难实例化。
现在我遇到困难的地方,在我看来,也是许多教程和问题比较薄弱的地方,是如何将这与依赖注入框架连接起来。当然,如果使用 CDI,我的示例现在会是这样的:
public class Car {
private Tyre tyre;
@Inject
public Car(Tyre tyre) {
this.tyre = tyre;
}
}
但是我不明白在这个阶段我得到了什么。我知道 Tyre 接口可能有多个实现,但是那样的话,你还需要在注入的顶部放置另一个注解,告诉框架插入哪种轮胎... 这与将具体实现放入签名中有什么区别呢?
在总结了我当前的知识和想法之后,我的问题基本上是以下进展的简单示例可能是什么样子的:
- 没有依赖注入的代码,并且存在一些问题
- 使用依赖注入的相同代码,其中解决了一些问题,但不是所有问题
- 使用依赖注入框架的相同代码(最好是 CDI),其中解决了所有问题。
英文:
I am struggling to understand the benefits of using a dependency injection framework, in my case CDI in a Jakarta EE context.
I think I see the point of the standard use case for dependency injection - it is often benefitial to "factor out" the concrete instantiation of an instance variable, so that
public class Car {
private Tyre tyre;
public Car() {
tyre = new Tyre();
}
}
becomes
public class Car {
private Tyre tyre;
public Car(Tyre tyre) {
this.tyre = tyre;
}
}
which has the benefit that now we can insert whatever tyre we like, which is handy for unit tests, for example, as Tyre might be difficult to instantiate.
Now where I am struggling, and I think where many tutorials and questions are a bit thin, is, how you can connect this with a dependency injection framework. Of course, my example would now read (when using CDI)
public class Car {
private Tyre tyre;
@Inject
public Car(Tyre tyre) {
this.tyre = tyre;
}
}
but I don't see why I have gained anything at this stage. I am aware that you could have more than one implementation of the Tyre interface, but then you would have to put another annotation on top of the injection to tell the framework which tyre is inserted... how is this any better than just putting the concrete implementation into the signature?
After summing up my current knowledge and thoughts, my question is basically how a baby example of the following progression could look like:
- Code without dependency injection and with some problems
- Same code with dependency injection where some, but not all problems are solved
- Same code with a dependency injection framework (preferrably CDI) where all problems are solved.
答案1
得分: 4
我对CDI不太熟悉,但可以就您的示例情况下的依赖注入(DI)框架一般性问题进行说明。
假设您有SummerTyre
和WinterTyre
,并且根据Season
季节需要使用其中之一。如果没有DI框架,每个想要创建Car
的类都必须依赖于Season
,并且必须包含创建正确的Tyre
的代码,尽管这实际上是Car
的实现细节。
DI框架允许您在其他地方进行配置(通常在main
函数或非常靠近它的地方),以便您的所有代码只依赖于正确配置的注入器,而无需其他任何内容。
在这种特定情况下,您可能会拥有一个SeasonalTyreFactory
,负责创建正确类型的Tyre
。Season
被注入到此工厂中,并且配置了注入器以在需要Tyre
时调用此工厂。现在,任何代码都可以请求一个Car
,而不必担心是否拥有正确的一组Tyre
!
英文:
I'm not familiar with CDI but I can speak about DI frameworks in general in the context of your example.
Suppose you have SummerTyre
and WinterTyre
, and you need to use one or the other depending on the Season
. Without a DI framework, every class that wants to create a Car
will have to depend on the Season
, and will have to contain code to create the right Tyre
s, even though this is essentially an implementation detail of the Car
.
What DI frameworks let you do is configure this stuff elsewhere (typically in main
or very close to that), so that all your code just depends on a properly configured injector and nothing else.
In this particular case, you'd probably have a SeasonalTyreFactory
that is responsible for creating the right kind of Tyre
. The Season
is injected into this factory, and the injector is configured to call this factory whenever it needs a Tyre
. Now any code can request a Car
without worrying about whether it's got the right set of Tyre
s!
答案2
得分: 2
从 DI 的角度来看,唯一真正的优势是您无需自己构建依赖关系树。
在您的示例中进一步进行,我们现在使用汽车:
Tyre myTyre = new Tyre();
Car theCar = new Car(myTyre);
好吧,这还不算太过于复杂,但想象一下一个真实应用程序,其中有数十个甚至数百个具有特定依赖关系的服务:
ApplicationConfiguration config = new ApplicationConfiguration(...);
GeneralDatabaseService dbAccess = new GeneralDatabaseService(conf);
UserRuleService userRules = new UserRuleService(config);
UserDatabaseService userDb = new UserDatabaseService(config, dbAccess, userRules);
// ...
// 还有 100 行类似的代码
// ...
ApplicationEntryPoint app = new ApplicationEntryPoint(conf, userDb, ...);
app.start();
在 CDI 容器中,容器会为您完成所有这些脚手架工作,实际上只需要这样做:
container.select(Car.class).get().drive();
// 或者
container.select(ApplicationEntryPoint.class).get().start();
除此之外,您还有上下文、不同范围的 Web 应用程序、事件处理、拦截器等等...
英文:
The only real advantage from the DI perspective is, that you don't have to build the
dependency tree yourself.
Going further in your example, we now use the car:
Tyre myTyre = new Tyre();
Car theCar = new Car(myTyre);
OK, that's not too overwhelming, but imagine a real application with dozens or hundreds
of services, which have their specific dependencies:
ApplicationConfiguration config = new ApplicationConfiguration(...);
GeneralDatabaseService dbAccess = new GeneralDatabaseService(conf);
UserRuleService userRules = new UserRuleService(config);
UserDatabaseService userDb = new UserDatabaseService(config, dbAccess, userRules);
...
// 100 more lines like this
...
ApplicationEntryPoint app = new ApplicationEntryPoint(conf, userDb, ...);
app.start();
In a CDI container, the container does all that scaffolding for you, and it boils down to
simply do:
container.select(Car.class).get().drive();
// or
container.select(ApplicationEntryPoint.class).get().start();
Apart from that, you have contexts, different scopes for web applications, event handling,
interceptors, and so on...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论