哪个更邪恶:不必要的单身人士还是上帝对象?
情况是这样的:我有一个班级做得太多了。 它主要用于访问配置信息,但它也有数据库连接。 它是作为单例实现的,因此这也使得单元测试变得困难,因为大多数代码与其紧密耦合。 这甚至会带来更大的问题,因为它会创建导入时依赖项(我们在 Python 中这样做),这意味着某些模块必须按特定顺序导入。 理想情况下,我想将其分为两个类并使其成为非单例。
幸运的是,我的雇主已经意识到这种测试是好的,并且愿意允许我进行这样的更改,如果它使代码更易于测试的话。 然而,我怀疑他们是否愿意让我花太多时间在这上面。 我宁愿逐步解决这个问题,也不愿过于激进。
因此,我在这里看到三个选择:
- 将配置对象分解为(单例)配置对象和(非单例)数据库对象。 这至少允许我删除数据库作为导入时依赖项。
- 使配置对象成为非单例并将其传递给需要它的对象。 我觉得这更好地满足了我们的短期需求,但我认为这将花费更多的时间。
- 做一些我没有想到你在答案中建议的事情。 :-)
那我该怎么办?
Here's the situation: I've got a class that is doing too much. It's mainly for accessing configuration information, but it also has the database connection. It's implemented as a singleton, so this also makes unit testing it difficult as most code is pretty tightly coupled to it. This is even more problematic as it creates an import-time dependency (we're doing this in Python), which means that certain modules must be imported in a certain order. Ideally, I'd like to both split this into two classes and make it a non-singleton.
Fortunately, my employer has warmed up to the fact that this kind of testing is good and is willing to permit me to make changes like this if it makes code more testable. However, I doubt that they will be willing to allow me to spend too much time on it. And I'd rather fix this incrementally rather than trying to be too radical.
So, I see three choices here:
- Break the configuration object into a (singleton) configuration object and a (non-singleton) database object. This would at least allow me to remove the database as an import-time dependency.
- Make the configuration object a non-singleton and pass it to objects that need it. I feel that this better addresses our short-term needs, but I think it would take significantly more time.
- Do something I hadn't thought of that you suggest in your answer. :-)
So what do I do?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我认为你们正朝着分成两个班级的方向发展。 您可能需要考虑使用工厂来根据需要创建数据库上下文/连接。 通过这种方式,您可以将连接视为根据需要创建/处置的工作实体单元,而不是在对象的生命周期中保留单个连接。 不过,YMMV。
至于配置,这是我发现单例可能是正确选择的一种情况。 我不一定会仅仅因为它很难进行单元测试而放弃它。 不过,您可能需要考虑构建它来实现接口。 然后,您可以在测试期间使用依赖项注入提供接口的模拟实例。 您的生产代码将被构建为在注入的值为 null 时使用单例实例,或者注入单例实例。 或者,您可以构造该类以允许通过私有方法重新初始化,并在安装/拆卸测试方法中调用它,以确保它具有适合您的测试的正确配置。 与后一种实现相比,我更喜欢前者,尽管当我无法直接控制界面时我也会使用它。
循序渐进地进行改变绝对是正确的方法。 如果可能的话,用测试包装当前功能,并确保这些测试在修改后仍然通过(当然,不是直接处理修改的测试)是确保不会破坏其他代码的好方法。
I think you are on track to separate into two classes. You might want to consider using a factory to create your database context/connection as needed. This way you can treat the connection as a unit of work entity that gets created/disposed as needed rather than keeping a single connection around for the life of the object. YMMV, though.
As for the configuration, this is one case where I find that a singleton can be the right choice. I wouldn't necessarily dump it just because it is hard to unit test. You might want to consider building it, though, to implement an interface. You can then use dependency injection to provide a mock instance of the interface during testing. Your production code would be built either to use the singleton instance if the injected value is null or to inject the singleton instance. Alternatively, you could construct the class to allow re-initialization via a private method and invoke this in your setup/teardown test methods to ensure that it has the proper configuration for your tests. I prefer the former to the latter implementation, though I've used it as well when I don't have direct control over the interface.
Making the changes incrementally is definitely the way to go. Wrapping the current functionality with tests, if possible, and making sure that those tests still pass after your modifications (though, not the ones dealing directly with your modifications, of course) is a good way to make sure you're not breaking other code.
不看你的代码就很难知道,但为什么不按照你说的去做——增量地做呢? 首先执行步骤1,拆分数据库。
如果速度很快,那么返回,现在您只有 1 个较小的对象(而不是 2 个)来停止成为单例。因此,第 2 步应该更快。 或者在这个阶段您可能会看到一些其他可以从单例中重构的代码。
希望您可以逐步减少单例中的内容,直到它消失,而无需在任何一步支付巨额时间税。
例如,如果配置的各部分是独立的,则也许可以一次将配置的一部分设为单例。 那么也许 GUI 配置在文件配置重构时保留了单例,或者类似的东西?
It's hard to know without seeing your code, but why not do what you say - do it incrementally? First do step 1, split out the database.
If that is quick then go back, and you now have only 1 smaller object to stop being a singleton rather than 2. So step 2 should be quicker. Or at this stage you might see some other code that can be refactored out of the singleton.
Hopefully you can step-by-step reduce what is in the singleton until it vanishes, without ever paying a huge time tax at any one step.
For example could perhaps one part of the configuration be made singleton at a time, if the parts of the configuration are independent. So maybe GUI configuration left singleton while fileconfiguration refactored, or something similar?
选项 1 是我在所有应用程序中所做的:配置对象单例和按需创建或注入的数据库对象。
将配置对象作为单例一直非常适合我。 只有一个配置文件,我总是想在应用程序启动时读取它。
Option 1 is what I do in all my apps: configuration object singleton and database objects created on demand or injected.
Having the cofiguration object as a singleton has always been a perfect fit for for me. There is only one configuration file and I always want to read it when the application starts.
请原谅我不懂任何Python,所以我希望任何伪代码都是有意义的......
我首先将对象分成两部分,这样你的单例的职责更小,然后我会采取剩余的配置单例并将其更改为普通类(就好像您要执行第二个建议一样)。
一旦我做到了这一点,我将创建一个新的包装器单例,它公开配置类的方法:
现在应用程序要做的第一件事就是将 ConfigurationClass 的实例注入到包装器中。
最后,您可以将当前依赖于单例的类移动到包装器单例(这应该是快速查找和替换)。 现在,您可以一次转换一个类,以通过其构造函数获取配置对象来完成第 2 阶段。您没有时间做的任何事情都可以使用包装器单例。 一旦你把它们全部搬过来,你就可以去掉包装纸了。
另一方面,您可以忽略用法的重构,只需将模拟的配置类注入到单例包装器中进行测试 - 本质上是穷人的依赖注入。
Excuse the fact that I don't know any Python, so I hope any pseudo code makes sense...
I'd split the object into two parts first so that the responsibilities of your singleton are smaller, then I'd take the remaining configuration singleton and change it into a normal class (as though you are going to perform your second suggestion).
Once I'd got that far I'd create a new, wrapper singleton that exposes the methods of the configuration class:
Now the very first thing the application does is inject an instance of the ConfigurationClass into the wrapper.
Finally you can move classes that currently rely on the singleton over to wrapper singleton (which should be a quick find and replace). Now you can convert the classes one at a time to take the config object in through their constructors to complete stage 2. Any that you don't have time to do can use the wrapper singleton. Once you have moved them all over you can get rid of the wrapper.
On the other hand you can ignore the refactoring of the usages and simply inject a mocked configuration class into the singleton wrapper for testing - essentially a poor man's dependency injection.