ServiceLocator和开闭原则

5

我想要:

  1. 让所有需要它们的类都能看到常用的服务
  2. 最少的样板代码
  3. 不牺牲可测试性!

这是一个小项目,我认为 DI 可能有点过头了,但也许我错了?无论如何,我一直在关注 Martin Fowler 描述的 ServiceLocator 模式

在客户端类的构造函数中,我有类似于这样的代码:

this->db = Locator::getDb();
this->log = Locator::getLogger();

然后,类的其余方法通过这些成员属性访问服务,例如:

this->fooModel = new fooModel(this->db);
fooItem1234 = this->fooModel->findById(1234);

然而,我也希望对于“model”对象(如上面的fooModel)具有这种级别的可见性,因为它们从几个不同的地方访问,并且没有必要拥有多个实例。
所以我的最初想法是扩展Locator来拥有一个::getFooModel(),但现在似乎我正在违反开闭原则,因为每次引入新的模型类都必须修改Locator。
为了满足OCP,我可以使用动态服务定位器(也在Fowler的页面上描述),但出于与他相同的原因,即它不够明确,我并不完全赞同这一点。
另一种解决方案是使我所有模型的方法静态。 所以:
fooItem1234 = FooModel::findById(1234);

我喜欢这个库,因为它没有样板代码。我只需要创建一个新的模型类,就可以在任何地方使用一行代码调用它。但是现在这个模型依赖于Locator来查找它的数据库连接,我对此感到有些犹豫。首先,如果我需要在不同的数据库连接上打开两个fooModels,那将会非常混乱或者不可能。尽管如此,目前我并不需要这样做,所以这个选项似乎有点诱人。
最后,还有DI。但像我上面说的,我认为这对于这个小项目来说可能太过复杂了。
结论:我有点困惑,希望从StackOverflow的大牛那里得到一些建议!
2个回答

7
你认为依赖注入对你的项目来说是否过于复杂了?像构造函数注入这样的DI模式比服务定位器(我认为是反模式)更简单、更干净。

我认为服务定位器是一种反模式,因为它对API的用户完全不透明,用户无法知道哪些依赖项需要放置;因此,你可以很容易地在Service Locator会抛出异常的上下文中调用对象的方法,而API并没有给你任何提示。

你不需要使用DI容器来使用DI。如果只有一个简单的项目,你可以使用称为Poor Man's DI的方式手动连接依赖项。


是的,我说DI会过度设计时,我的意思是使用容器,抱歉。非常感谢您的回答!当您说构造函数注入时,您是说我应该将dbconn和logger对象传递到依赖它们的类的构造函数中吗?如果是这样,那么这实际上就是我正在做的事情。然后,出于某种原因,我决定在每个类的构造函数原型中添加记录器是不好的。但是现在您让我想一想,似乎比我尝试使用此ServiceLocator更有意义。 - oops
1
很好。那么你通常会做的是堆叠或包装依赖项。你可能会有很多低级服务,但通常可以将其中两个或三个包装在有意义的对象中,然后只注入其中一个,而不是三个低级服务。您可以重复此操作多次,以使单个类的依赖关系数量保持较少。 - Mark Seemann
将不完全相关的东西(例如上面示例中的DBconn和记录器)堆叠/包装到一个名为“config”的对象中,这样做是否不好? - oops
如果在你所建模的领域中是合理的,那听起来是个绝佳的主意;如果它们真的没有关联,那就不是了。最终,我们要跨越所有这些障碍来使代码更易于维护,我们只有让它尽可能直观,才能成功。 - Mark Seemann
好的..我猜“相关”的定义可能会有所不同 :) 我本来想问你另一个问题,但我觉得我们正在远离原来的话题,所以我把它发布在这里:http://stackoverflow.com/questions/1900562/is-this-a-sane-implementation-of-constructor-injection 如果你有时间,请去看看! - oops

3

...并且没有必要拥有多个实例。

你正在混淆概念。只需要一个类的实例来运行应用程序,并不意味着全局可用是个好主意。使用 DI 时,您不会改变基数 - 仍然只有一个实例。您所更改的是指向该实例的变量的范围。这是有区别的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接