针对接口编程:是否为所有域类编写接口?

时间:2020-03-06 14:54:40  来源:igfitidea点击:

我同意,针对接口进行编程是一种好习惯。在大多数情况下,在Java中,"接口"在此意义上是指语言构造接口,因此我们可以编写一个接口和一个实现类,并且在大多数情况下使用该接口而不是实现类。

我想知道这是否也是编写域模型的好习惯。因此,例如,如果我们有一个域类Customer,并且每个客户都可能有一个Orders列表,那么通常也可以编写接口ICustomer和IOrder。客户还会有一个IOrder列表而不是Orders吗?或者仅在域模型真正由域驱动的情况下才使用域模型中的接口,例如我们至少有两种不同类型的订单?换句话说,我们是仅根据域模型中的技术需求,还是仅在相对于实际域真正合适时才使用接口?

解决方案

编写接口"仅仅是因为"令我感到浪费时间和精力,更不用说违反了KISS原则。

当它们实际上对表示相关类的常见行为有用时,而不是仅仅作为花哨的头文件,我会写它们。

不要过度设计系统。如果我们发现自己有几种类型的Orders,并认为为Orders声明一个接口而不是在需要时重构它是合适的。对于领域模型,特定接口在开发的整个生命周期中发生很大变化的可能性很高,因此尽早编写接口几乎没有用。

我通常只在较小的项目上使用有意义的接口。但是,我最近的工作是一个大型项目,几乎每个域对象都有一个接口。这可能是过大的,而且肯定很烦人,但是我们进行测试并将Spring用于依赖项注入的方式需要它。

我们主要使用Wicket,Spring和Hibernate编写Web应用程序,并使用Spring Beans的接口,例如服务和DAO。对于这些类,接口是完全有意义的。但是我们也为每个领域类使用接口,我认为这太过分了。

即使我们确定只有一种具体类型的模型对象,使用接口也会使模拟和测试更加容易(但是如今,有一些framworks可以自动生成模拟类,即使对于具体的Java类也是如此) JTestR,Spring,Groovy ...)

但是我甚至更经常使用接口作为服务,因为在测试期间将它们模拟掉更为重要,并且针对接口进行编程可以考虑封装之类的问题。

接口是隔离组件的一种极好的方法,可用于单元测试和一般依赖性管理。话虽如此,我通常更喜欢抽象类,以便至少在其中卸载一些常见行为,而不是强制接口带来一些重复。现代化的IDE可以轻松快捷地生成接口,因此它们的工作量不大:-)

我建议保持精简和敏捷,直到我们需要做任何事情为止,然后再让它在需要时让IDE进行重构。

在IDEA / eclipse中,当我们决定需要一个具体的类时,将一个具体的类转换为一个接口是相当琐碎的。

然后,如果我们有很多接口实现要注入代码中使用" new"的位置,请对Spring或者Guice使用依赖注入

如果我们将其用于单元测试,则为域类编写接口可能很有意义。我们在单元测试中使用Mock对象。因此,如果我们具有域对象的接口,而域对象本身尚未准备好,但是客户端可以在模拟对象的帮助下测试其对接口的使用。

Intefaces还可以为域模型测试接口的多种实现。因此,我不认为这总是过分杀伤力。

我认为针对接口进行编程的主要原因是可测试性。因此,对于域对象,只需坚持使用POJO或者POC#O :)等,即,仅使类不添加任何特定的框架,以防止它们脱离不同的构建和运行时依赖性,仅此而已。
不过,为DAO创建接口是一个好主意。

在需要接口之前(无论是用于测试还是用于体系结构)编写接口都是过分的。

另外,手动编写接口是浪费时间。我们可以使用Resharper的重构"拉成员",使其在几秒钟内从特定类中创建新接口。与IDE集成的其他重构工具也应具有类似的功能。

不,我只使用域对象上的接口来保持它们的松散耦合。对我来说,使用接口开发自己的代码的主要目的是,在进行单元测试时,我可以轻松地创建模拟。我看不到模拟域对象的意义,因为它们不具有服务层或者DAO层类所具有的依赖关系。

这当然并不意味着要偏离使用域对象中的接口。在适当的地方使用。例如,最近我一直在研究一个Web应用程序,其中不同类型的域对象对应于永久链接页面,用户可以在其中发表评论。因此,这些域对象中的每一个现在都实现" Commentable"接口。然后,所有基于注释的代码都被编程为Commentable接口,而不是域对象。

这是我要经历的另一件事,尤其是对于生成的域和DAO对象。许多接口都太具体了。假设许多域对象都有一个ID和一个状态字段,为什么它们不共享一个公共接口?这使我感到沮丧,这是不必要的扁平(继承方式)领域模型。

我们从所有内容中提取接口,只是因为它有助于测试(模拟)和AOP之类的东西。 Eclipse可以自动执行此操作:重构->提取接口。

而且,如果以后需要修改类,则可以使用Refactor-> Pull Up ...将所需的方法上载到接口。

*之所以这样做,是因为我需要它来创建域对象的代理。

实际上,这个问题是关于"针对接口编程"的常见误解的一个例子。

我们会看到,这是一个很好的原则,但这并不意味着很多人认为这意味着什么!

"编写接口的程序,而不是实现"(摘自GoF书)仅意味着,而不是我们应该为所有内容创建单独的接口。如果有一个" ArrayList"对象,则将变量/字段/参数/返回类型声明为" List"类型。客户端代码将仅处理接口类型。

在约书亚·布洛赫(Joshua Bloch)的"有效Java"(Effective Java)书中,在"项目52:通过对象的接口引用对象"中更清楚地表达了原理。它甚至以粗体显示:

It is entirely appropriate to refer to an object by a class rather than an
  interface if no appropriate interface exists.

对于单元测试,情况完全取决于所使用的模拟工具的功能。使用我自己的工具JMockit,对于使用接口和依赖注入的代码,就像使用从被测代码内部实例化的最终类的代码一样,我可以轻松编写单元测试。

因此,对我来说,答案是:始终使用已经存在的接口,但是如果没有充分的理由(并且可测试性本身不应该是一个),则避免创建新的接口。