Java代码风格 -- 接口与抽象类

12

我的一位新合作者在审核我编写的代码时告诉我,她不习惯在Java代码中直接使用接口,例如:

public interface GeneralFoo { ... }

public class SpecificFoo implements GeneralFoo { ... }

public class UsesFoo {
     GeneralFoo foo = new SpecificFoo();
}

相反,期望看到

public interface GeneralFoo { ... }

public abstract class AbstractFoo implements GeneralFoo { ... }

public class SpecificFoo extends AbstractFoo { ... }

public class UsesFoo {
     AbstractFoo foo = new SpecificFoo();
}

我可以看出当所有的SpecificFoo通过AbstractFoo共享功能时,这种模式是有意义的,但是如果各种Foos具有完全不同的内部实现(或者我们不关心特定的Foo如何执行Bar,只要它能做到),在代码中直接使用接口是否有害?我意识到这可能在某种程度上是个纷争问题,但我想知道是否存在第二种风格的优势或第一种风格的缺点。


可能是接口与基类的重复问题。 - thecoop
1
@thecoop:几乎是那个问题的副本,但仍然不同。这个问题比较接口类 vs 接口基类-类,而另一个问题则比较接口类 vs 基类-类。此外,这个问题是针对Java的。 - Goran Jovic
她听起来像一个顽固的烦人家伙! - Jonathan
8个回答

15

如果您不需要一个抽象类,其中包含所有实现共有的某些细节,那么实际上也没有必要使用抽象类。通常,应用程序会增加复杂性,是因为有一些看似需要支持尚未定义的未来功能。坚持使用可行的方法,稍后再进行重构。


2
给这个点赞。在程序中,我们经常会发现一个接口只被一个抽象类部分实现,而只有一个基类提供完全定义的功能。在成熟的代码中,这种情况需要重构来合并抽象和基类,并根据代码结构进一步重构以将结果接口/单一实现转换为一个类。 - Edwin Buck

5

不,她缺乏经验,而不是合适的人选。最好使用接口,为了冗余而编写冗余的抽象超类是多余的。

UsesFoo 应该关心接口所指定的行为,而不是其依赖项的超类。


4

对我来说,“她不习惯”这个理由不够充分,应该要求她详细说明。

就我个人而言,我会使用您的解决方案,因为:

  1. AbstractFoo是多余的,在当前情况下没有增加任何价值。

  2. 即使需要AbstractFoo(用于某些附加功能),我也总是使用最低所需类型:如果GeneralFoo已经足够了,那么我会使用它,而不是从它派生的某个类。


3

这取决于你的问题。

如果你只使用接口,那么如果所有的类都有一个相同的方法,那么就必须要重复实现它(或将其移到Util类中)。

另一方面,如果你编写了一个中介抽象类,你解决了这个问题,但是现在你的子类可能不是另一个类的子类,因为Java中没有多继承。如果已经需要扩展某个类,则不可能实现多继承。

因此,简而言之,这是一个权衡。在你的特定情况下使用更好的方法。


2

直接在代码中使用接口并没有什么害处。如果有的话,Java 就不会有接口了。

直接使用接口的缺点包括无法访问未在接口中实现的类特定方法。对于编写不良的接口或添加了大量“其他”功能的类来说,这是不可取的,因为您失去了访问所需方法的能力。然而,在某些情况下,这可能反映出创建接口时做出的不良设计选择。没有详细信息,很难知道。

直接使用基类的缺点包括最终忽略接口,因为它不经常被使用。在极端情况下,接口成为人类阑尾的代码等效物; “存在但几乎没有功能”。未使用的接口不太可能得到更新,因为每个人都会直接使用基本抽象类。这使得从实际尝试使用接口的人的角度来看,您的设计会默默腐烂。在极端情况下,无法通过接口处理扩展类以执行某些关键功能。

个人而言,我喜欢通过其接口返回类,并在内部存储它们的最低子类成员。这提供了对类在其封装中的亲密了解,强制人们在外部使用接口(保持其最新状态),并且类的封装允许可能的未来替换而不会引起太多麻烦。


2
我很好奇第二种样式是否有优势,或者第一种样式是否有缺点,我可能会忽略的因素。
第一种接口风格的原因:
1.通常,设计是这样的,接口是概念的公共接口,而抽象类是概念的实现细节。例如,在集合框架中考虑List和AbstractList。List是客户端真正需要的东西;较少的人知道AbstractList,因为它是帮助接口的供应商(实现者)而不是类的客户端(用户)的实现细节。
2.接口耦合性更低,因此更灵活,以支持未来的更改。
3.使用更清晰地表示类需求的那一个,通常是接口。例如,通常使用List而不是AbsrtactList或ArrayList。使用接口,将来的维护者可能更清楚地了解这个类需要某种类型的List,但它并不特别需要AbstractList或ArrayList。如果这个类依赖于一些AbstractList特定属性,即需要使用AbstractList方法,则使用AbstractList list = ...而不是List list = ...可能是一个提示,表明这段代码依赖于AbstractList的某些特定内容。
4.使用较小、更抽象的接口可能简化测试/模拟,而不是使用抽象类。

1

有些人认为按照AbstractFoo签名声明变量是一种不好的做法,因为UsesFoo类与foo的某些实现细节耦合在一起。

这会导致灵活性降低 - 您无法将foo的运行时类型与实现GeneralFoo接口的任何类交换;您只能注入实现AbstractFoo后代的实例 - 这样留下了更小的子集。

理想情况下,像UsesFoo这样的类只需要知道它们使用的协作者的接口,而不需要了解任何实现细节。

当然,如果在abstract class AbstractFoo implements GeneralFoo中没有必要声明任何abstract内容 - 即没有所有子类都会重用的公共实现 - 那么这只是浪费额外文件和层次结构。


0

首先,我丰富地使用抽象类和接口。

在使用接口之前,我认为你需要看到它的价值。我认为设计方法是这样的,我们有一个类,因此我们应该有一个抽象类,因此我们应该有接口。

首先,为什么需要一个接口,其次,为什么需要一个抽象类。似乎她正在添加东西,只是为了添加东西。解决方案必须有明确的价值,否则您所说的代码没有价值。

从经验上看,您应该看到她解决方案的价值。如果没有价值,则解决方案是错误的;如果无法向您解释清楚,那么她不理解自己为什么要这么做。

简单的代码是更好的解决方案,当您需要复杂性、灵活性或者她从解决方案中获得的任何感知价值时,请进行重构。

展示价值或删除代码!

哦,还有一件事,请查看Java库代码。它使用她应用的抽象/接口模式吗?不会!


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