10

这是对Zed Shaw 很久以前在他的博客中发表的某些评论的回应。

然后,专家们将闲逛去实现他们的 Flaming Tower of Babel,没有任何评论,非常复杂的模拟启用测试,确保每个单独的类都有一个接口,并以“Impl”结束每个类,因为这是最佳实践。

我以同等的方式使用 Spring 和 Google Guice,我注意到这些框架确实使用Impl后缀,但很少使用。在我的代码中,我在任何地方都使用接口,因为有人告诉我它可以更容易地模拟等。我对这个问题有一个幼稚的理解吗?(也许模拟框架可以与抽象类或类一起使用,我不知道。我从未尝试过)对于我的具体实现,我选择了 Spring 约定,即在实现名称前加上 Default 一词。

e.g. BottleOpener (interface) is implemented by DefaultBottleOpener (class)

在这个问题上的最佳做法是什么?

UPDATE1我发现从方法返回接口很有用,因为我总是可以返回一个匿名类。

4

13 回答 13

17

这不仅仅是嘲笑。在 Spring 的情况下,它是关于动态代理和面向方面的编程。这就是 Spring 处理所有类型的事务、远程处理和方面的方式。

我将接口用于存储库、服务等,但我不会将接口放在模型对象或其他任何实现不太可能改变的东西上。

接口将 API 与它们的实现方式分开。如果您的实现没有改变,那么接口就没有多大意义。

Zed Shaw 是一位了不起的作家和开发者,但他说的话持保留态度。我认为他沉迷其中的一些夸张的夸张是为了娱乐价值。他说得有道理(“不要因为自称权威的人告诉你这是‘最佳实践’而做事”),但他所说的方式是部分戏剧。

于 2010-02-18T02:58:14.070 回答
6

The book Interface Oriented Design has a helpful way of thinking about interfaces - that they belong to the client, not the provider. So rather than thinking about whether a given class should be represented by an interface, think about what interfaces a given class might want to interact with, then find or write classes that conform to those interfaces. An interface may be very pared-down, compared to a longer list of public features in an implementing class - and of course a given class can implement several interfaces. Looking for a one-to-one correspondence between classes and interfaces, even thinking about interfaces as representations of full classes, isn't as useful a way of thinking about interfaces.

于 2010-02-18T05:43:48.620 回答
6

最佳做法是:

  • 选择一个约定并保持一致;和
  • 不要过分让所有东西都实现一个接口。
于 2010-02-18T02:57:56.010 回答
2

Any class that provides a 'service' to another collaborator should have an interface - you would potentially want to chop and change implementations to achieve the desired behavior. As an example you could replace a twitter service with an email service easily.

Any class that represents a 'value' object should not need an interface as it serves a single purpose. In testing you would not even need to mock these objects, just used them directly as they define leaf nodes of your object graph.

于 2010-02-18T05:35:27.730 回答
2

这在很大程度上是一个主观问题。最好的响应可能是您可以尽可能多地对代码进行单元测试,但不要让界面创建妨碍完成工作。如果你的代码很快就被淹没了,就把它删掉。

也就是说,我已经看到有 400 个类和这些类的 400 多个接口的代码。仅仅通过糟糕的命名约定来挖掘一堆代码就足以让我胆战心惊了。另一方面,我正在处理没有接口的第三方专有库。模拟这些对象变得很痛苦。

于 2010-02-18T02:59:04.193 回答
2

我看不出为每个类创建一个接口有什么好处。这只会迫使你编写一堆代码两次。

我认为不需要创建一个接口来区分 API 和实现。带有签名的类的公共函数列表就是 API。如果该类需要不属于 API 的 worker-bee 函数,那么它们不应该是公共的。我没看到

当在给定的上下文中,它有一个有用的目的时,我会创建一个接口。基本上这意味着:

(a) 在实践中,我最常见的原因是实现/模拟多重继承。如果我需要 A 扩展 B 但我还需要一个接受 C 的函数并且我想将 A 传递给该函数,那么我必须使 B 或 C 成为接口而不是父类并使其 A 扩展 B 实现C 或反之亦然。

(b) 当我为一个库或实用程序函数创建一个 API 时,该 API 将由该库或实用程序之外的某些东西实现,通常由多个东西实现。Java 库中的“Comparable”接口就是一个例子。排序实用程序需要一个函数来比较一对对象。当程序使用调用程序中定义的对象调用排序实用程序时,不能期望排序实用程序知道如何比较它们,因此调用程序必须提供比较函数。比较函数的签名在逻辑上位于调用者可以为其对象实现的接口中。

(c) 如果我想在不透露实现的情况下发布接口。这可能是出于专有原因,但实际上通常是因为另一个人或团队将实现该接口。

于 2010-02-18T05:03:10.977 回答
1

好吧,我“听说”在 SmallTalk 中,你总是在实际实现之前开始定义接口......所以我想,这真的取决于你想要实现的目的和设计目标......

于 2010-02-18T02:58:19.197 回答
1

在对象上拥有接口,并且让对象只通过接口相互通信,这是一件好事。

请注意,我指的是对象,而不是集合或其他本质上是“数据”的东西。

然而,让一个类实现“IClassName”或类似的,并在整个代码库中使用该模式反映了对接口概念的理解不足。接口通常应该由消费类声明,作为“嘿,这是我需要的服务”的声明。这样,界面代表了两个对象之间的交互,而不是对象对世界的单一视图。它还有助于保持职责分离,因为每个类都在处理自己的理想接口,因此可以帮助保持域逻辑与实现逻辑分离。

(编辑)

这实际上是关于间接和抽象之间的区别。与您的 API 直接匹配的接口只是间接接口。直接满足消费者需求的接口是一种抽象,因为它声明消费者想要做什么,并隐藏了如何完成的所有信息。

Zed Shaw 对此有一篇很好的文章。

http://www.zedshaw.com/essays/indirection_is_not_abstraction.html

于 2010-02-18T03:19:22.860 回答
1

我认为这取决于您的设计。如果你想使用依赖注入或模拟对象测试,那么接口是有意义的。此外,如果您计划有多个实现,那么肯定需要接口来说明行为的合同。

如有疑问,也许采取敏捷方法?

只需要一个啤酒开瓶器

class BeerBottleOpener // life's good

现在需要一个红酒开瓶器

class WineBottleOpener // life's still good

稍微重构一下

interface BottleOpener;
class BeerBottleOpener implements BottleOpener;
class WineBottleOpener implements BottleOpener;
// now living the life!  ;-)

顺便说一句,对于模拟测试,我使用了需要接口的EasyMock 。现在切换到Mockito,它可以模拟类并且更明确且更简单。

于 2010-02-18T04:37:40.620 回答
1

我认为将所有具体类隐藏在接口后面不会真的出错。

在某些情况下它是多余的,但它不会花费你太多。

甚至可以为使域对象实现接口提供一个案例。好处:

  • 您可以从客户端代码中隐藏丑陋的细节(例如 JPA 注释、仅由 ORM 框架使用的设置器)
  • 就像使用 DAO 或服务一样,您可以针对抽象进行编程。考虑一个用户抽象——您的业务逻辑不依赖于它们的存储方式和位置(例如数据库或操作系统),如果您必须使用第三方代码,您可以轻松使用适配器模式。
于 2010-02-18T04:00:43.700 回答
0

在接口上过度使用是一种非常现实的可能性。忽略使用虚拟调度进行每个方法调用的[可忽略的]性能损失,它还为系统引入了更多选项。它实际上会使调试和保护软件变得更加困难。如果您的方法/函数接受任何旧接口实现者,则您已决定接受实现者可能错误地甚至恶意地实现了接口。您的程序/库/框架如何允许变化是设计过程的重要组成部分。

于 2010-02-18T03:12:03.480 回答
0

给所有东西一个接口(或者更一般地为可能会不使用的灵活性而设计)被称为过度工程。如果您可能永远不会拥有多个具体的实现,那么接口就是臃肿。避免构建未使用的灵活性的代码更容易理解,因为没有接口掩盖您只有一个实现这一事实,您知道您正在处理的具体类型并且代码更容易推理。

这也是我最喜欢的反对模拟的论点之一:它在您的代码中引入了人为的要求,例如能够拥有不止一种实现,而您将只能拥有一种“真正的”实现。

于 2010-02-18T03:13:31.220 回答
0

任何事情到痴迷的程度都是错误的。这些只是工具,我们是主人。

于 2010-02-19T23:59:27.407 回答