【问题标题】:Is "inject everything" a bad practice in Android?“注入一切”在Android中是一种不好的做法吗?
【发布时间】:2017-05-18 11:31:52
【问题描述】:

在研究依赖注入时,我发现了一些建议 inject everything 的方法和其他建议 it's not necessary to do so 的方法。

在我当前的项目中,我关于依赖注入的经验法则是“如果该类是由我创建的,我将其设为可注入”。换句话说,只有像SimpleDateFormatArrayListHashMap 这样的类在我的项目中是新的。我这样做的意图是,一旦在Activity 中调用Injector.getApplicationComponent().inject(this),我就可以在任何地方@Inject 任何类。基本上我所有的类都有一个带有@Inject的非参数构造函数。

我主要使用 DI,因为我认为一旦 new 运算符被 Dagger 生成的类独占使用,它会提高性能和内存使用率。但是我从 Dagger 1 的开发者那里读到了post 说 DI 对性能没有影响,使用基本上是为了减少样板。

第一个问题是:

  • Dagger 2 在 Android 应用程序中没有任何性能优势?

我的项目运行没有问题,我认为“注入一切”的方法有助于更好地组织,尽管有一些缺点。

使用这种方法的一个例子是下面的类:

public class TimelineEntryAdapter {

@Inject
Provider<TwitterEntry> mTwitterProvider;

@Inject
Provider<InstagramEntry> mInstagramProvider;

@Inject
Provider<FacebookEntry> mFacebookProvider;

@Inject
TimelineEntryComparator mComparator;

@Inject
public TimelineEntryAdapter() {
}

第二个问题是:

  • 在 Android 中注入所有内容是一种不好的做法吗?

如果第二个问题的答案是“否”,那么有没有更好的方法来处理非参数构造函数来创建类?因为当我创建一个带有@Inject 注解的非args 构造函数并且一个类需要一些参数才能使用时,我必须使用setters

public class SavelArtist {

private MusicBrainzArtist mMusicBrainzArtist;

private DiscogsArtist mDiscogsArtist;

private List<SavelTweet> mTweetList;

private SpotifyArtist mSpotifyArtist;

private List<SavelInstagram> mInstaTimeline;

private List<SavelFacebook> mFacebookTimeline;

private List<SavelRelease> mReleases;

@Inject
Provider<SavelRelease> mReleaseProvider;

@Inject
public SavelArtist() {
}

public void setMusicBrainzArtist(MusicBrainzArtist mbArtist) {
    mMusicBrainzArtist = mbArtist;
}

public void setDiscogsArtist(DiscogsArtist discogsArtist) {
    mDiscogsArtist = discogsArtist;
}

public void setTweetList(List<SavelTweet> tweetList) {
    mTweetList = tweetList;
}

public void setSpotifyArtist(SpotifyArtist spotifyArtist) {
    mSpotifyArtist = spotifyArtist;
}

public void setInstaTimeline(List<SavelInstagram> instaTimeline) {
    mInstaTimeline = instaTimeline;
}

public void setFacebookTimeline(List<SavelFacebook> fbTimeline) {
    mFacebookTimeline = fbTimeline;
}

所有参数都可以在构造函数上设置,一旦在流程中同时获取。

【问题讨论】:

  • 您想提供您希望能够模拟的所有内容。您不必提供不需要模拟的内容(例如 LinearLayoutManager)。
  • 就像 Epic 所说的,它用于轻松切换课程。它可能不是模拟的,它可能是“这个做日志记录,这个不做日志记录”。您可以在运行时进行热交换,或在编译时进行交换。请记住,如果您使用 Dagger,请确保您实际上是在“使用”它,通过在一个地方拥有多个课程。否则你只是无缘无故地添加了一个 DI 框架(我曾经接手过一个项目。增加了复杂性并没有真正的好处)

标签: java android dependency-injection dagger-2


【解决方案1】:

在研究依赖注入时,我发现了一些建议注入所有内容的方法,而另一些方法则表示没有必要这样做。

您引用的froger_mcs blog entry 并不主张注入所有内容。它非常清楚地指出:

这篇文章的目的是展示我们可以做什么,而不是我们应该做什么。

它继续说明注入所有东西的缺点:

如果您想将 Dagger 2 用于项目中的几乎所有内容,您将很快看到生成的代码使用了 64k 方法计数限制的一大块进行注入。

现在,关于你的问题:

Dagger 2 在 Android 应用中没有任何性能优势?

虽然 Dagger 2 提供了优于其他基于反射的 DI 框架(例如 Guice)的性能优势,但它并没有声称比通过调用构造函数手动构造对象图提供任何性能优势。您可以自己检查生成的类,看看它们确实最终仍会调用构造函数。

在 Android 中注入所有内容是一种不好的做法吗?

好吧,让我们来看看以下非常常见的 Android 代码:

Intent nextActivity = new Intent(this, NextActivity.class);
startActivity(nextActivity);

我们是否应该提取一个IntentFactory 并使用Dagger 2 注入它只是为了避免这里的new 关键字?在这一点上,很容易接近迂腐。 other answer you quoted 中关于注射剂和新剂之间区别的建议更加灵活和优雅。

继续你的下一个问题:

如果第二个问题的答案是“否”,那么有没有更好的方法来处理非参数构造函数来创建类?因为当我使用 @Inject 注释创建一个非参数构造函数并且一个类需要一些参数才能使用时,我必须使用 setter:

使用 setter 是错误的参数方法。你应该区分依赖参数。依赖项通常与对象本身具有相同的生命周期。在对象的生命周期中,可以使用不同的参数调用为该对象公开的方法。

【讨论】:

  • 非常好的和详细的答案。我不会想到,实际上我必须在 Java-EE 依赖策略和 android 之间做出改变。谢谢 :) (我在 Java-EE 中使用 CDI,我的答案基于这个神话)
  • 很好的答案,大卫。感谢您的关注。
【解决方案2】:

我不是依赖管理方面的专业人士,但我在公司中使用它,所以这是我的看法。

注入一切都基本好了。我将在其他地方使用的所有东西(其他模块、类、包)都制作成可注入的,并且只有静态的东西(带有隐藏构造函数的静态类),只在内部使用的东西是不可注入的。

它有什么好处? 那么依赖系统将负责获取实例并丢弃它。它会自动清理。此外,在类上使用作用域将使您能够创建一个在整个应用程序中始终只有一个实例的类(多个线程访问它)或使其可重用(每个需要它的线程都是一个自己的实例)。

我也认为你可以像这样清理你的类:

@Reusable (Maybe a good Idea?)
public class SavelArtist {

private MusicBrainzArtist mMusicBrainzArtist;

private DiscogsArtist mDiscogsArtist;

private List<SavelTweet> mTweetList;

private SpotifyArtist mSpotifyArtist;

private List<SavelInstagram> mInstaTimeline;

private List<SavelFacebook> mFacebookTimeline;

private List<SavelRelease> mReleases;

private Provider<SavelRelease> mReleaseProvider;

public SavelArtist() {
}

@Inject
public void init(Provider<SavelRelease> mReleaseProvider) {
  this.mReleaseProvider = mReleaseProvider;
}

考虑总是声明类的范围。是单例吗?会重复使用吗?一旦应用程序变得复杂和庞大,这个小细节可以为您节省时间。

使用方法 init 来声明所有注入变量的优点是代码看起来很干净,易于维护,因为注入的所有内容都在这个位置。但这实际上是偏好:)

【讨论】:

  • 感谢您的回复!我试图实施您的解决方案,它奏效了! :D 但是现在,我有另一个疑问:您从我的构造函数中删除了@Inject,我知道您希望我改用@Provides。如果我不想拥有@Provides 怎么办?我需要打电话给new SavelArtist()?它不会打破“注入一切”的链条吗?
  • 如果您不想使用@Provides,您可以再次将其添加到构造函数中。这只是我的一个想法,甚至可能是一个混乱,因为我使用 Java CDI 而不是 dagger-2,并且不需要注释构造函数
  • 太棒了!现在我知道了。谢谢。
猜你喜欢
  • 1970-01-01
  • 2019-04-19
  • 2012-05-27
  • 2015-05-31
  • 2019-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多