尽管依赖项注入 (也称为“ DI”)是一种在OOP中组成对象的自然技术(在Martin Fowler引入该术语之前就已知道),但Spring IoC , Google Guice , Java EE6 CDI , Dagger和其他DI框架将其转变为反模式。
我将不讨论反对“ setter注入”(例如在Spring IoC中 )和“现场注入”(例如在PicoContainer中 )的明显论点。 这些机制仅违反了面向对象编程的基本原理,并鼓励我们创建不完整的可变对象,这些对象在应用程序执行过程中会塞满数据。 请记住:理想对象必须是不可变的 , 并且不能包含setter 。
相反,让我们讨论一下“构造函数注入”(例如Google Guice中的方法 )及其与依赖注入容器的结合使用 。 我将尝试说明为什么至少将这些容器视为冗余。
什么是依赖注入?
这就是依赖项注入(与普通的旧对象组成没有真正的区别):
public class Budget {
private final DB db;
public Budget(DB data) {
this.db = data;
}
public long total() {
return this.db.cell(
"SELECT SUM(cost) FROM ledger"
);
}
}
对象data
称为“依赖关系”。
Budget
不知道它正在使用哪种数据库。 它需要从数据库中获得的所有功能,就是能够通过cell()
方法使用任意SQL查询来获取单元cell()
。 我们可以使用DB
接口的PostgreSQL实现实例化Budget
,例如:
public class App {
public static void main(String... args) {
Budget budget = new Budget(
new Postgres("jdbc:postgresql:5740/main")
);
System.out.println("Total is: " + budget.total());
}
}
换句话说,我们正在将依赖项“注入”到新的对象budget
。
这种“依赖注入”方法的替代方法是让Budget
决定要使用的数据库:
public class Budget {
private final DB db = new Postgres("jdbc:postgresql:5740/main");
// class methods
}
这非常脏,导致1)代码重复,2)无法重用和3)无法测试等。无需讨论原因。 很明显。
因此,通过构造函数进行依赖注入是一种了不起的技术。 好吧,真的没有技术。 更像Java和所有其他面向对象语言的功能。 预计几乎所有对象都希望封装一些知识(也称为“状态”)。 这就是构造函数的用途。
什么是DI容器?
到目前为止,一切都很好,但是这里面是阴暗的一面-依赖项注入容器。 它是这样工作的(让我们以Google Guice为例):
import javax.inject.Inject;
public class Budget {
private final DB db;
@Inject
public Budget(DB data) {
this.db = data;
}
// same methods as above
}
注意:构造函数带有@Inject
注释。
然后,我们应该在应用程序启动时在某处配置一个容器:
Injector injector = Guice.createInjector(
new AbstractModule() {
@Override
public void configure() {
this.bind(DB.class).toInstance(
new Postgres("jdbc:postgresql:5740/main")
);
}
}
);
一些框架甚至允许我们在XML文件中配置注入器。
从现在开始,我们将无法像以前一样通过new
运算符实例化Budget
。 相反,我们应该使用刚创建的注射器:
public class App {
public static void main(String... args) {
Injection injector = // as we just did in the previous snippet
Budget budget = injector.getInstance(Budget.class);
System.out.println("Total is: " + budget.total());
}
}
注入自动发现,要实例化Budget
它必须为其构造函数提供一个参数。 它将使用我们在注射器中实例化的Postgres
类的实例。
这是使用Guice的正确和推荐的方法。 不过,有一些甚至更暗的模式,这是可能的,但不建议这样做。 例如,您可以使喷射器成为单例,并在Budget
类中直接使用它。 这些机制甚至被DI容器制造商都认为是错误的,因此,让我们忽略它们,重点关注推荐的方案。
这个是来做什么的?
让我重申并总结不正确使用依赖项注入容器的场景:
- 现场注入
- 二传手注射
- 将注入器作为依赖项
- 使注入器成为全局单例
如果我们把它们都放在一边,剩下的就是上面解释的构造函数注入。 那对我们有什么帮助? 我们为什么需要它? 为什么我们不能在应用程序的主类中使用普通的new
?
如果使用XML,我们创建的容器只是向代码库添加了更多行,甚至添加了更多文件。 它除了增加了复杂性之外没有增加任何东西。 如果遇到以下问题,我们应该永远记住这一点:“哪个数据库用作预算的论点?”
正确的方式
现在,让我向您展示一个使用new
构造应用程序的真实示例。 这是我们在rultor.com中创建“思维引擎”的方式 (完整的类在Agents.java
):
final Agent agent = new Agent.Iterative(
new Array(
new Understands(
this.github,
new QnSince(
49092213,
new QnReferredTo(
this.github.users().self().login(),
new QnParametrized(
new Question.FirstOf(
new Array(
new QnIfContains("config", new QnConfig(profile)),
new QnIfContains("status", new QnStatus(talk)),
new QnIfContains("version", new QnVersion()),
new QnIfContains("hello", new QnHello()),
new QnIfCollaborator(
new QnAlone(
talk, locks,
new Question.FirstOf(
new Array(
new QnIfContains(
"merge",
new QnAskedBy(
profile,
Agents.commanders("merge"),
new QnMerge()
)
),
new QnIfContains(
"deploy",
new QnAskedBy(
profile,
Agents.commanders("deploy"),
new QnDeploy()
)
),
new QnIfContains(
"release",
new QnAskedBy(
profile,
Agents.commanders("release"),
new QnRelease()
)
)
)
)
)
)
)
)
)
)
)
),
new StartsRequest(profile),
new RegistersShell(
"b1.rultor.com", 22,
"rultor",
IOUtils.toString(
this.getClass().getResourceAsStream("rultor.key"),
CharEncoding.UTF_8
)
),
new StartsDaemon(profile),
new KillsDaemon(TimeUnit.HOURS.toMinutes(2L)),
new EndsDaemon(),
new EndsRequest(),
new Tweets(
this.github,
new OAuthTwitter(
Manifests.read("Rultor-TwitterKey"),
Manifests.read("Rultor-TwitterSecret"),
Manifests.read("Rultor-TwitterToken"),
Manifests.read("Rultor-TwitterTokenSecret")
)
),
new CommentsTag(this.github),
new Reports(this.github),
new RemovesShell(),
new ArchivesDaemon(
new ReRegion(
new Region.Simple(
Manifests.read("Rultor-S3Key"),
Manifests.read("Rultor-S3Secret")
)
).bucket(Manifests.read("Rultor-S3Bucket"))
),
new Publishes(profile)
)
);
印象深刻? 这是真正的对象组成。 我相信这是应该实例化正确的面向对象应用程序的方式。
和DI容器? 我认为,它们只会增加不必要的噪音。
相关文章
您可能还会发现以下有趣的帖子:
翻译自: https://www.javacodegeeks.com/2014/10/di-containers-are-code-polluters.html