【问题标题】:Guice puzzler: Batch scoped Encapsulated ContextGuice 谜题:批处理范围的封装上下文
【发布时间】:2012-12-12 19:56:24
【问题描述】:

我们正准备开始在我们的保险数据转换平台中使用 Guice,我遇到了一个有趣的场景,在 Guice 文档或我找到的任何帖子中似乎都没有直接解决。

我们的平台在几个重要领域使用封装上下文 (EC) 模式。例如,假设我们正在处理一组 10 个策略。每当我们开始处理新策略时,我们希望构造一个PolicyContext 对象并初始化诸如策略编号、状态和公司之类的属性。这个PolicyContext 是转换过程中涉及的许多类的依赖项。

请注意,PolicyContext(以及我们应用程序中的其他 *Context 对象)是一个紧密关注特定域领域的值对象(代表基本的、普遍需要的策略信息)。我很想知道你们中的模式专家是否仍然认为这是一种反模式(正如 Misko Hevery 在http://misko.hevery.com/2008/07/18/breaking-the-law-of-demeter-is-like-looking-for-a-needle-in-the-haystack/ 中所讨论的那样),即使这些是纯粹的价值对象并且当然不代表“厨房水槽”。 ”

目前,我们以最糟糕的方式管理PolicyContext:我们有一个静态全局变量policyContext,并且每当我们开始处理新策略时都会调用policyContext.initialize(String company, String state, String policyNum)

我的目标是让 Guice 以架构上优化的方式管理这些上下文对象,以便从概念上讲,每当我们开始处理新策略时:

  1. Guice 丢弃了旧的PolicyContext
  2. Guice 使用来自数据库的 company/state/policyNum 参数构造了一个新的、不可变的 PolicyContext(无异味初始化方法)。
  3. Guice 将已经构造的PolicyContext 注入到所有需要它的类中。

这是我的初步方法:

  1. 创建自定义范围——类似于http://code.google.com/p/google-guice/wiki/CustomScopes 的 Guice 批处理范围示例——其中批处理的边界由外部确定。有了这个范围,我们开始处理一个新的策略,我们可以 1) 结束前一个“批次”并开始一个新的。 :我为什么不能完全按照上述 URL 中列出的方式使用 Guice 批处理范围示例?
  2. 由于PolicyContext 没有依赖关系,我们将对所有 构造函数参数使用AssistedInject(这似乎有点奇怪)。假设我们采用这种方法并生成一个PolicyContextFactory,那么我们开始处理新策略的地方就会有如下代码:

    …
    scope.exit();
    scope.enter();
    @Inject private PolicyContextFactory policyContextFactory; 
    policyContextFactory.create(company, state, policyNum); // the parameters come from a database record.
    // Note that we don’t need to actually store the created instance; it will be injected elsewhere into various class constructors.
    …
    

这看起来是最优的吗?我知道可能有更简单的方法(例如,每当我们处理新策略时,创建一个新的PolicyContext 特定注入器,这实际上会创建一个新的PolicyContext)。然而,这是架构的核心方面,所以我真的不想妥协。

我知道,另一种选择是在这种情况下避免使用 DI,而只使用具有单独 createget 方法的静态 PolicyContextManager 类,其中前一种方法是丢弃当前的工厂PolicyContext 并创建/存储一个新的,而后一种方法只返回“活动”PolicyContext)。但是我的代码最终会做手动 DI,因为我会写很多像methodThatNeedsPolicyContext(PolicyContextManager.get(), …) 这样的代码。由于我们无论如何都打算开始使用 Guice,所以这种方法似乎不是最优的。

顺便说一句,对于那些试图深入了解 DI 的人,我强烈推荐 Dhanji Prasanna 的“依赖注入”。这本书侧重于 Guice 和 Spring,绝对是必不可少的,因为它比我遇到的任何其他书都深入得多。

感谢您的帮助!

【问题讨论】:

    标签: java scope guice


    【解决方案1】:

    似乎您链接的SimpleScope 几乎完全符合您的需求,因为您希望避免传递您的上下文,并且您的自定义范围将确保任何@PolicyScoped 绑定(大概只有您的上下文和它们的内容)已经准备好(“播种”)。您还将获得一些不错的多线程功能,尽管您可以通过将静态引用转换为静态 ThreadLocal 来获得这些功能。

    您必须在 enterexit 调用之间完全注入策略范围的对象图,或者您选择的任何名称。请注意,如果您将 PolicyContext 注入构造函数或字段(将其保存到对象的状态),则您的对象实例现在特定于该策略。这可能看起来很明显,但是再一次,队友漫不经心地注入或缓存dueDateCalculator 可能没有意识到它被隐式构造为仅适用于策略 #8675-309 的到期日期计算器,并且它将为策略 #5550- 提供错误的答案187.特别是任何需要策略范围依赖的@Singleton 对象都应该使用提供者,否则即使您已经退出范围,单例也会“记住”该策略;这是“范围扩大注入”和Prasanna discusses it at length 的示例。

    你可能会发现坚持让你的队友从不直接注入PolicyContext,而是总是注入Provider<PolicyContext>(你get for free if PolicyContext is injectable)更简单。这样一来,您就不必再考虑在构建对象时哪个策略处于活动状态,而是信任在该对象的方法运行时收到的 PolicyContext。

    如果一个对象没有依赖关系,你就不需要 Guice 来创建它——这只是矫枉过正。一旦对象产生了如此多的依赖项以至于手动构建很痛苦,就很容易将对象的创建移动到 Guice。非必要时不要这样做。

    最后,关于封装上下文,我碰巧相信 EC 模式是一种有效的重构,只要上下文没有逻辑并且整个对象包适用于上下文出现的位置。如果您可以向我辩护说 Context 中的每个项目在您注入上下文的时间中有 80% 被使用,那么代码可能更短且更容易遵循并且您赢了。请记住,依赖注入的好处之一是添加或删除依赖项非常容易,因此从注入一个单独绑定的 Context 属性到注入两个单独的 Context 属性再到直接注入整个 Context 变得非常容易(并且重复尽可能多的上下文)。

    不过,这只是我的看法。希望对您有所帮助!

    【讨论】:

    • 非常感谢杰夫。清楚地说明并解决了一些重要的细微差别。我确实有几个相关的问题,但我会单独发布。
    • +1 用于注入提供程序和创建自定义范围。不要认为您需要绑定来注入提供程序 BTW。
    • @RobbieV 正如我在“免费获取”评论中提到的,如果 Guice 可以注入 Foo,它可以注入 Provider<Foo> 而无需额外的代码。如果Foo 是一个具体的类,它可以被隐式绑定,不需要绑定FooProvider<Foo>,但如果Foo 是一个接口,你仍然需要在某个地方绑定它。我确实在上面说“绑定Provider<PolicyContext>”,但我的意思是“注入`Provider”,但现在已修复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多