大量构建一种实现。DI无望吗?使用服务定位器?


14

假设我们有1001个客户端,它们直接构造其依赖关系,而不接受注入。根据我们的老板,重构1001不是一个选择。实际上,甚至不允许我们访问其源代码,而只能访问类文件。

我们应该做的是“现代化”这1001个客户所经历的系统。我们可以重构自己喜欢的一切。依赖关系是该系统的一部分。还有一些依赖关系我们应该更改为具有新的实现。

我们想要做的是能够配置依赖关系的不同实现,以满足众多客户的需求。可悲的是,DI似乎不是一个选择,因为客户端不接受构造函数或setter的注入。

选项:

1)重构客户端使用的服务的实现,以使其执行客户端现在需要的功能。砰,我们完成了。不灵活。不复杂。

2)重构实现,以便将其工作委托给它通过工厂获取的另一个依赖项。现在,我们可以通过重构工厂来控制它们都使用哪种实现。

3)重构实现,以便将其工作委托给它通过服务定位器获取的另一个依赖项。现在,我们可以通过配置服务定位器来控制它们都使用哪种实现,该服务定位器可能只是hashmap对象的字符串,并且需要进行一些强制转换。

4)我什至没有想到的东西。

目标:

在不增加毫无意义的复杂性的情况下,将因设计欠佳的旧客户端代码拖到将来而导致的设计损失最小化。

客户不应该了解或控制其依赖项的实现,但是他们坚持使用来构建它们new。我们无法控制,new但可以控制他们正在构建的类。

我的问题:

我没有考虑什么?


Doc Brown的问题

您是否真的需要在不同的实现之间进行配置?出于什么目的?

敏捷。很多未知数。管理层希望变革的潜力。只失去对外界的依赖。还测试。

您是否需要运行时机制或只是编译时机制来在不同的实现之间进行切换?为什么?

编译时间机制可能就足够了。除测试外。

您需要在实现之间切换哪种粒度?一次全部?每个模块(每个模块包含一组类)?每堂课?

在1001中,任何一次都只能运行一次。一次更改所有客户端使用的内容可能很好。但是,对依赖项的单独控制可能很重要。

谁需要控制开关?只有您/您的开发人员团队?管理员?每个客户自己吗?还是客户的代码的维护开发人员?那么,机械师需要多么容易/稳健/万无一失?

开发测试。管理员随着外部硬件依赖性的变化而变化。它需要易于测试和配置。


我们的目标是证明该系统可以快速重建和现代化。


实施开关的实际用例?

一种是,在硬件解决方案准备就绪之前,一些数据将由软件提供。


1
我不认为通过将工厂或定位器存储在全球状态中,您将不会取得太大的成就。何必?您要测试替代依赖项的客户端吗?
Basilevs

考虑使用自定义类加载器。
Basilevs

出于好奇,这个系统是做什么的?
总理

Answers:


7

好吧,我不确定我是否完全了解受支持的解决方案的技术细节及其确切差异,但是恕我直言,您首先需要确定您真正需要哪种灵活性。

您必须问自己的问题是:

  • 您是否真的需要在不同的实现之间进行配置?出于什么目的?

  • 您是否需要运行时机制或只是编译时机制来在不同的实现之间进行切换?为什么?

  • 您需要在实现之间切换哪种粒度?一次全部?每个模块(每个模块包含一组类)?每堂课?

  • 谁需要控制开关?只有您/您的开发人员团队?管理员?每个客户自己吗?还是客户的代码的维护开发人员?那么,机械师需要多么容易/稳健/万无一失?

考虑到这些问题的答案后,请选择您认为最简单的解决方案即可为您提供所需的灵活性。如果您需要付出更多的努力,请不要实施您不确定“以防万一”的灵活性。

作为对您的答案的答复:如果手头至少有一个实际用例,请使用它来验证您的设计决策。使用该用例来找出哪种解决方案最适合您。如果“工厂”或“服务定位器”为您提供了所需的东西,或者您还有其他需要,请尝试一下。如果您认为两种解决方案都适合您的情况,请掷骰子。


好问题!请参阅更新。
candied_orange

更新了实际用例。
candied_orange

@CandiedOrange:我不能给你一些鱼,我只能帮你自己钓鱼:-)
布朗

明白了 我只是凝视着水面,想知道我是否需要更好的诱饵或其他钓鱼孔。我很乐意做适当的DI,但是这种情况似乎不允许这样做。
candied_orange

1
@CandiedOrange不要挂在做DI的愿望上,因为它比其他任何东西都“好”或“更好”。DI是解决问题的一种特定解决方案(有时甚至是多种解决方案)。但是,它并不总是最“合适”的解决方案。不要爱上它...像对待它一样。这是一个工具。
svidgen '17

2

只是为了确保我做对了。您有一些由某些类组成的服务,例如C1,...,Cn,有许多直接调用的客户端new Ci(...)。因此,我认为我同意您的解决方案的总体结构,即使用一些新的D1,...,Dn,不错的现代类创建一个新的内部服务,并注入其依赖项(允许通过堆栈进行此类依赖),然后重写Ci,除了实例化和委托到Di。问题是如何做到这一点,您在2和中建议了几种方法3

给...类似的建议2。您可以在整个Di内部使用依赖项注入,然后创建一个R负责组合对象图的常规组合根(如果认为合适,请使用框架)。推R后面的静态工厂,并让每一个Ci得到它的Di通过。例如,您可能有

public class OldClassI {
    private final NewClassI newImplementation;

    public OldClassI(Object parameter) {
        this.newImplementation = CompositionRoot.getInstance().getNewClassI(parameter);
    }
}

从本质上讲,这是您的解决方案2,但它将所有工厂与其余的依赖项注入一起收集到一个位置。


我同意这一点。只是不确定这与选项3中的服务定位符getInstance()有何不同?您是否希望每次调用都构建一个新实例?
candied_orange

@CandiedOrange这可能只是用法上的冲突,对我来说,服务定位符实际上是从类型到对象的哈希图,而组成根只是一个带有一堆构造其他对象的方法的对象。
walpen '17

1

从简单的,没有花哨的单一实现开始。

如果您以后需要创建其他实现,则可以在何时做出实现决策。

如果需要“安装时”灵活性(每个客户端安装使用单个静态实现),则仍然不需要做任何花哨的事情。您只提供了不同的DLL或SO(或任何您的lib格式)。客户只需要在lib文件夹中放入正确的文件夹即可。

如果需要运行时灵活性,则只需要一个精简适配器和一个实现选择器机制。无论您使用工厂,定位器还是IoC容器,都几乎无济于事。适配器和定位器之间的唯一重要区别是A)命名和B)返回的对象是单例对象还是专用实例。IoC容器与工厂/定位器之间的最大区别是谁叫。(通常是个人喜好问题。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.