Depending who you speak to, putting @Inject on a constructor is either akin to whipping your own grandmother or is such a blindingly obvious thing to do that the question is ridiculous.
根据与您交谈的人的不同,将@Inject放在构造函数上要么类似于鞭打自己的祖母,要么是一件令人眼花obvious乱的事情,以至于这个问题很荒谬。
But which is right?
但是哪个是对的?
波比人 (The Bobites)
These folk have been fully converted to the teachings of Uncle Bob — the father of clean code and clean architecture. Abstraction is our redemption and those who choose to ignore abstraction will burn in the fiery pits of hell.
这些人已经完全转换为Bob叔叔的教义-干净的代码和干净的建筑之父。 抽象是我们的救赎,那些选择忽略抽象的人将在火热的地狱中燃烧。
Or, more simply, Dagger is an implementation detail — a third party library. Our codebase should be able to function regardless of how DI is implemented. Say a library is scattered throughout your code base — this is bad.
或者,更简单地说,Dagger是实现细节-第三方库。 无论如何实现DI,我们的代码库都应该能够运行。 假设库分散在您的整个代码库中,这很不好。
Codebases live for a long time, and at some point that library is going to have breaking changes, or become unsupported, or a new shiny library will come out. In an ideal world, changing the library means only a few small changes to a small part of your codebase.
代码库的生存时间很长,有时库将发生重大更改,或者变得不受支持,否则将出现新的闪亮库。 在理想的世界中,更改库意味着仅对代码库的一小部分进行少量更改。
If Dagger implementation details (i.e. @Inject) are scattered throughout your codebase, your codebase becomes resistant to change, not to mention the rest of the codebase has added complexity.
如果Dagger实现细节(即@Inject)散布在您的整个代码库中,则您的代码库将变得难以更改,更不用说其余的代码库增加了复杂性。
Belief: Never use Dagger goodies like @Inject on constructors!
信念:切勿在构造函数上使用@Inject之类的Dagger东西!
Principle to live by: Abstraction is king
生存原则:抽象为王
飞船 (The Shipits)
These folk have seen examples of all those clean architecture code bases and honestly they look more trouble than they’re worth. We’re only trying to build an app, not architect the Golden Gate Bridge.
这些人看到了所有这些干净的体系结构代码库的示例,说实话,它们看起来比他们值得的麻烦更多。 我们仅尝试构建一个应用程序,而不是架构师金门大桥。
Dagger has this fancy annotation processing and code generation functionality that makes our lives easier, why on earth would we not use it?
Dagger具有这种精美的注释处理和代码生成功能,使我们的生活更轻松,为什么我们不使用它呢?
Why would we write a DI definition for every single class in an object graph — duplicating the objects constructor — so any changes to the constructor need made in several places? After all, Dagger will do the heavy lifting for us with one itsy bitsy @Inject annotation.
为什么我们要为对象图中的每个单个类编写一个DI定义(复制对象构造函数),以便需要在多个地方对构造函数进行任何更改? 毕竟,Dagger将为我们提供繁重的@Inject注释。
And let’s be honest, DI frameworks only change once every 2–4 years, what’s the point of abstracting something that more than likely won’t change for the entire time we’ll be working on the project?
老实说,DI框架每2-4年仅更改一次,那么抽象一个在我们进行项目的整个过程中很可能不会更改的东西有什么意义呢?
Belief: Always use @Inject on constructors!
信念:始终在构造函数上使用@Inject!
Principle to live by: Don’t Repeat Yourself (DRY)
赖以生存的原则:不要重复自己(DRY)
When you come down to it, abstraction and DRY code are two fundamental principles of programming — each equally correct. But, in this case, to use one is to break the other. This in itself is nothing new, there’s so many principles of programming that almost any choice you make is bound to break one principle or another.
当您深入到它时,抽象和DRY代码是编程的两个基本原理-每个都正确。 但是,在这种情况下,使用其中一种会破坏另一种。 这本身并不是什么新鲜事物,编程的原理如此之多,以至于您做出的几乎任何选择都必然会破坏一种或另一种原理。
But if that’s the case, how to choose which principle is more important, so which one you should adhere to? In order to make that choice, you need to evaluate each approach by something.
但是,如果是这样,那么如何选择更重要的原则,那么您应该坚持哪个原则呢? 为了做出选择,您需要通过某种方式评估每种方法。
Many principles of programming have a basis in saving development time. Whether that's sacrificing time now to spend longer implementing a feature which is then incredibly quick to change in the future. Or choosing the quick implementation route now knowing that you’ll pay the debt (and then some) of that saved time on maintenance in the future.
编程的许多原理都有节省开发时间的基础。 现在是否要花费时间来花费更长的时间来实现一项功能,而该功能在未来会Swift地改变。 或者现在就选择快速实施路线,因为您知道自己将来会偿还(节省了)节省下来的维护时间上的债务(然后还清了一部分)。
It’s often easier to worry about the present and choose the quick implementation route, even when the time debt you incur is much bigger than the time saved.
即使当您承担的时间债务比节省的时间大得多时,通常也更容易担心现在的情况并选择快速的实施途径。
Principles we follow — quasi religiously — are to trick our present selves into making better choices which help us write code that takes less time for our future selves, or some other poor developer that takes over our codebase to maintain or change in the future.
我们遵循的原则(半信半疑地)是欺骗我们目前的自我做出更好的选择,以帮助我们编写使自己将来花费更少时间的代码,或者其他一些贫穷的开发人员接管我们的代码库,以维护或更改未来。
So perhaps we can evaluate which principle to choose based on time — the amount of time it takes to implement, maintain, and change.
因此,也许我们可以根据时间评估选择哪种原则,即实施,维护和更改所花费的时间。
Let’s see how both methods stack up.
让我们看看这两种方法是如何叠加的。
@无处不在 (@Inject everywhere)
In this hypothetical codebase @Inject is used all over. You can’t look at an object constructor without seeing that pesky annotation. For the developers that wrote it, you can count the seconds to implement with one hand.
在此假设的代码库中,全部使用@Inject。 您必须先查看该讨厌的注解,才能查看对象构造函数。 对于编写它的开发人员,您可以用一只手计算实现的秒数。
![Image for post](https://miro.medium.com/freeze/max/9999/1*Jwtr1SR7DKfkvwfVLm7c2g.gif)
Maintenance is also pretty simple. Any time an object in the constructor was changed, Dagger re-generated the object graph definition automatically.
维护也很简单。 每当更改构造函数中的对象时,Dagger都会自动重新生成对象图定义。
![Image for post](https://miro.medium.com/freeze/max/9999/1*EEgymK_qgTge0bOqZbzvdg.gif)
But, one day, a bright spark decided Dagger bad, Koin good. So the team embark on the unenviable task of ridding the codebase of Dagger and adding Koin — coincidently the same team will at some point in the near future be planning on how to migrate to Hilt.
但是,有一天,明亮的火花决定了匕首不好,科恩很好。 因此,团队开始了一项艰巨的任务:删除Dagger的代码库并添加Koin-巧合的是,同一团队将在不久的将来计划如何迁移到Hilt。
Let’s see how they got on.
让我们看看他们如何相处。
![Image for post](https://miro.medium.com/freeze/max/9999/1*SgLxh0oCbWm1hLd2YWgMCg.gif)
And don’t forget, sometimes this part wouldn’t even be needed. After all @Inject is part of Java (JSR330) and is used by other JVM dependency injection libraries like Guice and Toothpick.
并且不要忘记,有时甚至不需要这部分。 毕竟@Inject是Java( JSR330 )的一部分,并由Guice和Toothpick等其他JVM依赖项注入库使用。
All in all this method has been pretty quick. Minimal lines of code needed written in the first place for DI to function, maintenance was a breeze, and removing Dagger from the codebase was super quick (if it even needs removed in the first place).
总而言之,这种方法很快。 首先,要使DI起作用,需要编写最少的代码行,维护起来很容易,并且从代码库中删除Dagger非常快捷(如果甚至需要首先删除它)。
How does abstraction compare?
抽象如何比较?
抽象DI (Abstracted DI)
In this codebase DI has been thoroughly abstracted. DI is fully inside a single package and none of the rest of the codebase has any idea DI exists, let alone what library is used to implement it. So an injection looks something like this:
在此代码库中,对DI进行了彻底的抽象。 DI完全放在一个包中,而代码库的其余部分都不知道DI存在,更不用说使用什么库来实现它了。 因此,注入看起来像这样:
![Image for post](https://miro.medium.com/freeze/max/9999/1*n9rK9YxE5utE4RGVECCDRw.gif)
These are more lines of code as we’re not using Daggers code generation so it has taken longer to write, but we’re sure to save that time in the future.
这些是更多的代码行,因为我们没有使用Daggers代码生成,因此花费了更长的时间编写,但是我们肯定会在将来节省时间。
Now during maintenance when a constructor argument needs added or removed.
现在在维护期间,需要添加或删除构造函数参数。
![Image for post](https://miro.medium.com/freeze/max/9999/1*NBEIttXQUmZFogc43eUBuA.gif)
Not ideal, every change really requires three changes, and let’s not forget the problem with non-DRY code.
这并不理想,每个更改实际上都需要进行三个更改,我们不要忘记非DRY代码的问题。
![Image for post](https://miro.medium.com/freeze/max/9999/1*KRwwP-CgjhaJs5GzlbQ18Q.gif)
So maintenance takes significantly longer. But the benefit really comes when switching DI libraries, not a single change needs made in the rest of your codebase. A genuine, if costly (in this case), benefit.
因此,维护需要更长的时间。 但是,真正的好处是切换DI库时,而不是在其余代码库中进行的单个更改。 真正的(如果是昂贵的话)收益(在这种情况下)。
In this slightly contrived example using @Inject is quite clearly quicker. Even in this short example ~1 minute is saved all round by using Dagger’s inbuilt code generation.
在这个稍作设计的示例中,使用@Inject显然更快。 即使在这个简短的示例中,使用Dagger的内置代码生成也可以节省大约1分钟的时间。
This may sound small, but imagine a codebase with multiple developers working for several years, the combined time saving can add up to days of development time. Not to mention the reduction in boilerplate code, after-all, who enjoys writing boilerplate?
这听起来似乎很小,但是可以想象一个有多个开发人员一起工作了几年的代码库,节省下来的时间总共可以增加几天的开发时间。 更何况样板代码的减少,毕竟,谁喜欢编写样板?
But there are drawbacks to using @Inject on constructors and less easily definable benefits to abstraction. For instance:
但是在构造函数上使用@Inject有缺点,而抽象的好处则较难定义。 例如:
- Classes don’t know about DI and DI info is limited to one layer, limiting the complexity overhead of the codebase. 类不了解DI,并且DI信息仅限于一层,从而限制了代码库的复杂性开销。
- Visualising the DI graph is easier with DI code in one package — new tools like Scabbard and the Dagger Visualiser in Android Studio 4.2 make this less important. 使用一个程序包中的DI代码,可视化DI图形更加容易-像Android Studio 4.2中的Scabbard和Dagger Visualiser这样的新工具使这一点变得不那么重要了。
- Implementations are hidden from the object graph so they can’t be accidentally injected. 实现从对象图中隐藏起来,因此不会被意外注入。
- Finding and replacing @Inject across an entire codebase will cause merge headaches if there are multiple active branches when it’s done. 如果完成时有多个活动分支,则在整个代码库中查找和替换@Inject将导致合并麻烦。
These are very valid concerns. But, do you think these concerns save more time than having DRY code and letting Dagger do the heavy lifting for you?
这些是非常有效的担忧。 但是,您是否认为这些问题比拥有DRY代码并让Dagger为您省力的事情节省了更多时间?
The answer for most codebases is probably no.
大多数代码库的答案可能是“否”。
结论 (Conclusion)
Every choice made while coding is a trade-off. Maybe in your situation it makes complete sense to fully abstract your DI. But before you make that decision, ask yourself, are you making that decision because someone told you to, or are you making that decision because it makes your specific codebase easier (and quicker) to implement, maintain, and change for you and future developers?
编码时做出的每个选择都是一个权衡。 也许在您的情况下,完全抽象您的DI完全有意义。 但是在做出决定之前,请先问自己,是因为有人告诉您做出决定,还是要做出决定,是因为这样做会使您和将来的开发人员更容易(更快)实施,维护和更改特定的代码库?
奖金 (Bonus)
Even when using @Inject regularly, one common use of manually calling constructors in Dagger modules is for when injecting interfaces rather than implementations. Dagger has a way of dealing with this without duplicating object constructors, @Binds to the rescue.
即使定期使用@Inject,在Dagger模块中手动调用构造函数的一种常见用法是在注入接口而非实现时使用。 Dagger有一种无需复制对象构造函数即可解决的方法,@Binds可以帮助您。
class ThisClassDoesFunStuffImpl @Inject constructor(
private val someUseCase: SomeUseCase
) : ThisClassDoesFunStuff
@Module
interface BindInterfacesModule {
@Binds
fun myInterface(thisClassDoesFunStuffImpl: ThisClassDoesFunStuffImpl): ThisClassDoesFunStuff
}
Vehemently disagree? Stick in a comment!
强烈不同意? 留下评论!
翻译自: https://proandroiddev.com/dagger-and-inject-on-constructors-do-or-dont-9d97e7c93f84