将类注入其他类被认为是错误的,一些论点是很难模拟一个类并将它们耦合在一起。
但是我看到很多开发人员每天都这样做,而且大多数模拟框架非常擅长模拟类并提供测试模拟,那么问题是什么?
答案 0 :(得分:4)
抽象类型(如接口)往往比具体类更稳定(随时间变化更小)。依赖更稳定的东西通常风险较小。将这些放在一起,与具体类相比,依赖接口的风险通常较小。
当我更多地依赖接口时,我也发现更容易检测到行为的重大变化。当界面必须改变时,一些重要的事情正在发生;当一些微不足道的事情发生时(令人惊讶地)导致界面发生变化,那么我将其解释为提高设计中抽象层次的信号。对于具体的课程,这个信号不会发出声音"对我来说非常清脆,干净,显而易见。
注入具体类创建的风险远远低于直接在客户端模块中实例化它们的风险。如果您不担心注入具体课程的风险,那么请等一段时间并等待风险成为问题。你可以决定你有多想。幸运的是,从重构类中提取接口是非常安全的,因为重构会发生。请记住,当需要时,提取客户端需要的最小接口,并且不要盲目地将每个具体的类方法放到接口上。 (界面隔离原则。)
答案 1 :(得分:3)
某些(有影响力的)人士,如罗伯特·马丁声称,在源代码中使用具体类的名称基本上是应该避免的,以防止“过于紧密耦合”。
让我引用他的“敏捷原则”一书:
高级模块不应该依赖于低级模块。两者都应该取决于抽象。 抽象不应该依赖于细节。细节应该取决于 抽象
并进一步:
考虑依赖于低级模块的高级模块的含义。它是包含应用程序的重要策略决策和业务模型的高级模块。这些模块包含应用程序的标识。然而,当这些模块依赖于较低级别的模块时,对较低级别模块的更改可能会对较高级别的模块产生直接影响,并可能迫使它们依次更改。 这种困境是荒谬的!高级别的政策制定模块应该影响低级详细模块。包含高级业务规则的模块应优先于包含实现详细信息的模块,并且与之无关。高级模块不应该以任何方式依赖于低级模块。
但当然,实际上,总会有平衡。当你非常相信
时然后在一个类上更喜欢接口没有多大意义。
但是 - 如果有疑问,接口是更好的方法。
我必须经常向人们解释他们使用PowerMock以允许模拟某些最终类/方法的想法是错误的方法;而更好的答案是将参数的类型改为某个界面。
答案 2 :(得分:1)
我在实践中看到的最明显的原因是你可能会失去DI框架的功能,这些功能有时对于应用程序的正常运行至关重要。此外,直到后来,当代码在生产中愉快地执行时,这些问题才会变得明显。
例如,spring框架(无疑是Java世界中最顶层的DI框架之一)依赖于使用装饰器模式来动态实现受AOP约束的接口。它动态创建一个从您的接口实现的类,该类将您的原始bean实例作为私有成员,并使用AOP逻辑装饰相关成员。
当直接注入类时,你禁用了DI框架执行AOP魔法的可能性,因为动态实现的结果类型将无法分配给你请求的具体类(我不确定spring将如何这样做 - 它将创建装饰器并抱怨它对注入的成员类型是不可分配的,或者更糟糕的是 - 注入未被装饰的成员,这被剥夺了AOP功能)。丢失注入bean的AOP功能可能会导致注入类的行为不正确。
永远不会使用类进行直接注入的值得注意的示例是依赖于@Transactional
注释的事务管理的持久性服务。如果以某种方式注入这样的服务,并且没有AOP,那么最终可能会破坏您的数据库,因为当抛出异常时,事务管理器将不会清理您的混乱。当然,当应用程序用户抱怨或应用程序本身行为不端时,您将意识到后果的后果。
有些人可以反对并建议在spring的AOP中使用cjlib而不是默认的AOP行为。这样可以使Spring扩展您启用AOP的类,而不是使用装饰器模式。虽然这允许你注入一个类,并且它的AOP仍然可以工作,但这种方法被认为是一种不好的做法,并且现在大多被拒绝接受装饰方法。其中的各种原因包括:
final
,则cjlib将失败。装饰可以更好地处理这个问题。