在Java中,你是否应该总是使用接口进行编码?

50

我理解编码到接口的原则 - 从接口中分离实现,允许接口的实现互换。

我是否应该为我编写的每个类编写接口,还是这太过头了?我不想在项目中将源文件数量翻倍,除非真的值得。

有哪些因素可以用来决定是否使用接口编码?


请参考以下问题:https://dev59.com/BHA75IYBdhLWcg3w6Nr8 - the_drow
谢谢。这正是我想要的。 - Tarski
始终首先使用接口可以鼓励严格的封装和可读性,换句话说,只需处理接口,甚至看不到实现。 - Zbyszek
6个回答

43

总的来说,我同意其他答案:在你知道或预料到变化和/或不同实现时使用接口,或者考虑可测试性。

但是,如果你不是很有经验,很难提前知道未来可能发生什么变化。我认为解决这个问题的方法是重构:只要不需要,就保持简单(= 不用接口)。当需要时,只需进行“引入/提取接口”重构(几乎所有良好的IDE都支持此功能)。

在提取接口时,只提取调用者实际需要的方法。如果提取出的方法并非真正相关,请毫不犹豫地提取出更多单独的接口。

重构的一个驱动因素可能是测试:如果只使用类无法轻松测试某些内容,则考虑引入一个或多个接口。这也将允许您使用Mocking,在许多情况下大大简化测试。

编辑:根据Tarski的评论,我意识到之前声明的一个更重要的情况/调整:
如果您提供外部API(用于其他[子]项目或真正发布到“野外”),则在API中使用接口(除了简单的值类)几乎总是一个好主意。
它将使您可以在不干扰客户端代码的情况下更改实现。只有在您还必须更改接口时才会出现问题。不破坏兼容性将非常棘手(如果不是不可能的话)。


很棒的答案。我遇到了完全相同的情况 - 我有一个被多个客户端项目使用的类,现在需要一个接口来提供另一种实现 - 我可以使用覆盖,但不够清晰简洁。问题是我需要重新编译所有现有的客户端来使用这个接口。就像你所说,当我开始的时候我并不知道我需要这个接口。 - Tarski
1
你对重新编译的理解是正确的,重构后必须进行一次重新编译。之后,实现将被很好地解耦,因此您可以自由更改/添加新的实现而无需重新编译。 - Sandor Murakozi
关于编辑,您可能想阅读以下内容:http://martinfowler.com/bliki/PublishedInterface.html - michaelsnowden

13

我在以下情况下使用它:

1) 当我想将不相关的模块/类解耦,并希望Java包依赖反映这一点。

2) 在构建依赖方面解耦很重要时

3) 当实现可能通过配置或运行时动态更改(通常是由于某些设计模式)

4) 当我想让一个类易于测试时,我将其“链接点”与外部资源接口连接,以便可以使用模拟实现。

5) 不太常见:当我想有一个选项来限制对某个类的访问时。在这种情况下,我的方法返回一个接口而不是实际的类,这样调用者就知道我不希望他对返回值执行其他操作。


+1 是为了测试性和访问限制。 - Dan Vinton

11
不是每个类都需要一个接口,这样做过度了。只有在需要从做什么与如何做分离的时候才使用接口,并且你确定实现可能会发生更改。看看 java.util 集合 API ,List 接口是列表的概括,但你可以看到有许多实现它的方式:ArrayList、LinkedList 等。如果你在设计一个具体类之后决定需要一个接口,然后通过添加接口破坏了客户端,该怎么办?是的,这就是发生的事情,你如何知道你不知道的东西呢?没有软件或方法可以修复这个问题。好消息是,从具体类中提取接口是一种易于重构的操作,对你和客户端来说都很容易。IDEs 通常处理这种操作,你和客户端应该充分利用它们。我认为层次结构,比如服务和持久性,应该始终有接口。任何可以代理的内容都应该有接口。如果你正在进行 Spring AOP,则希望使用方面装饰的任何内容都应该有接口。模型或值对象(例如 Person、Address 或 Phone)不应该有接口。不可变值对象也不应该有接口。其余的则存在灰色地带,需要你自行判断。

3
如果你不知道你将来需要抽象化,该怎么办?例如,你编写了一个没有接口的类,你的客户端依赖于这个类,但是当你需要另一个实现时,你无法提取出一个接口而不破坏二进制兼容性。 - Tarski
1
^ this。另外,创建一个接口并让类实现它真的那么难吗?这样做真的需要更多的时间吗? - GreenieMeanie

3
长话短说,不需要。作为应用程序的创建者,您对可能发生的变化有很好的想法。在我看来,一些设计模式书籍过于疯狂,因为它们似乎总是推动设计接口而不管实现。有时候,这只是过度设计。对于我的最新应用程序(小型),我有一个数据库接口,因为我认为有可能将其移植到Web并切换到MySQL。当然,在现实中,稍后从我的现有类创建数据库接口并稍作更改就很容易了,只需稍后添加“implements myDbInterface”即可。但这仅适用于我是唯一的开发人员的情况。对于更大的团队或不同类型的产品,答案可能不同。只需根据自己的判断使用最佳实践即可(当然,在阅读所有好的SO答案之后)。

1

我认为当可插拔性和潜在的不同行为很重要时,应该使用接口。服务是完美的选择。一个人可能想要一个默认实现,但模拟等另外一种方式也可以。这显然意味着应该有一个接口,因为至少会有两种形式。

另一方面,值类型应该是final类。它们不应该被扩展。值类型包括像Suburb、MimeType之类的东西。如果有这些的变化,请使用组合。作为目标,我尽量在我的值类型中放入尽可能少的行为,如果有的话。验证它所包装的简单类型可能是它最复杂的事情。例如,一个颜色类,它需要r/g/b,应该验证每个分量是否在0到255之间,仅此而已。


1

当你期望某个行为在未来发生变化时,可以使用接口。或者使用能够清晰处理变化的类。

例如:

public static void main(String s[])
{
 Employee e1=new Employee(new Salesman());
 Employee e2=new Employee(new Manager());

 system.out.println(e1.performaction());
 system.out.println(e1.performaction());
}

Class Employee
{

//which changes frequently
Authority a;

public Employee(Authority a)
{
this.a=a;
}    
public String performAction()
{
a.action();
}
}

//------------------------------------------------------------------

// 这是一个分隔符,用于区分代码中不同的部分。

interface Authority
{ 
public String action();
}

class Manager implements Authority 
{ 
public String action()
{
 returns "I manages People";
}

 class SalesMan implements Authority 
{
  public String action() 
{
 returns "I Sell things Company makes ";
}
}

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