不使用接口是否有坏习惯?[关闭]


40

我很少使用接口,并发现它们在其他代码中很常见。

另外,我很少在代码中创建子类和超类(同时创建自己的类)。

  • 这是一件坏事吗?
  • 您是否建议更改此样式?
  • 这种风格有副作用吗?
  • 这是因为我没有从事任何大型项目吗?

10
这是哪一种语言?

2
除了什么语言,这是什么范式?
阿曼多

1
“接口”的概念是否超越语言?Java具有内置的Interface类型,但是C ++开发人员也经常创建接口(以I前缀),并且它们都具有相同的目的。
莉吉特

4
@Ricket:不,不是。问题在于C ++提供了多个类继承。在C ++中,“接口”更具概念性,实际上,我们大多使用它们将其定义为抽象基类的方法。#
DeadMG 2011年

2
@Ricket否,尤其是如果您使用的是功能语言而不是程序语言或OOP语言。
可替代

Answers:


36

您可能要使用接口的原因有几个:

  1. 接口适用于您的应用程序需要许多可能不相关的对象类型来提供某些功能的情况。
  2. 接口比基类更灵活,因为您可以定义一个可以实现多个接口的实现。
  3. 在不需要从基类继承实现的情况下,接口会更好。
  4. 在无法使用类继承的情况下,接口很有用。例如,结构不能从类继承,但是它们可以实现接口。

http://msdn.microsoft.com/zh-CN/library/3b5b8ezk(v=vs.80).aspx

接口就像编程中的其他任何东西。如果不需要它们,请不要使用它们。我已经看到它们被广泛地用作样式问题,但是如果您不需要接口提供的特殊属性和功能,那么“仅仅因为”它们并没有带来任何好处。


13
没有指定他的语言,而您的语言太具体了-例如,在C ++中,结构确实可以从类继承,并且您可以乘以继承非抽象的基础。
DeadMG

2
这个答案似乎是C#,但如果您选择了#4,它也适用于Java,并且仅适用于C ++,因为C ++中当然允许多重继承。
莉吉特

2
另外,我不同意最后的陈述。我认为程序员有很多好的做法应该做,但不要做,因为他们觉得自己不需要它们。我认为询问者在环顾四周,看到其他人都在做这件事是非常明智的,并认为也许他也应该这样做,但首先要问“为什么”。
莉吉特

3
@Ricket:为什么是我回答的四个要点。如果这些原因均不适用,则您不需要界面。
罗伯特·哈维

17

类继承和接口都有它们的位置。继承的意思是“是”,而接口提供的契约定义了“行为”。

我想说,经常使用接口根本不是一个坏习惯。我目前正在阅读Bill Wagner撰写的“有效的C#-50种改善C#的特定方法”。项目编号22指出,并用I引号“优先于继承定义和实现接口”。

通常,当我需要定义概念上相关的类型之间常见行为的特定实现时,我使用基类。我更经常使用接口。实际上,我通常在开始创建类时就从为类定义一个接口开始……即使最后我不编译该接口,我发现通过定义该类的公共API也可以帮助开始。从一开始就上课。如果我发现我有多个实现该接口的类,并且实现逻辑是相同的,则只有这样我才问自己,在这些类型之间实现一个公共基类是否有意义。

Bill Wagners的几句话引述...

所有派生类都立即包含该行为。向接口添加成员会破坏实现该接口的所有类。它们将不包含新方法,并且将不再编译。每个实施者必须更新该类型以包括新成员。在抽象基类和接口之间进行选择是一个问题,即随着时间的推移如何最好地支持抽象。接口是固定的:您将接口作为任何类型可以实现的一组功能的合同发布。基类可以随时间扩展。这些扩展成为每个派生类的一部分。可以混合使用这两种模型,以在支持多个接口的同时重用实现代码。” 它们将不包含新方法,并且将不再编译。每个实施者必须更新该类型以包括新成员。在抽象基类和接口之间进行选择是一个问题,即随着时间的推移如何最好地支持抽象。接口是固定的:您将接口作为任何类型可以实现的一组功能的合同发布。基类可以随时间扩展。这些扩展成为每个派生类的一部分。可以混合使用这两种模型,以在支持多个接口的同时重用实现代码。” 它们将不包含新方法,并且将不再编译。每个实施者必须更新该类型以包括新成员。在抽象基类和接口之间进行选择是一个问题,即随着时间的推移如何最好地支持抽象。接口是固定的:您将接口作为任何类型可以实现的一组功能的合同发布。基类可以随时间扩展。这些扩展成为每个派生类的一部分。可以混合使用这两种模型,以在支持多个接口的同时重用实现代码。” 您以任何类型可以实现的一组功能的合同形式发布接口。基类可以随时间扩展。这些扩展成为每个派生类的一部分。可以混合使用这两种模型,以在支持多个接口的同时重用实现代码。” 您以任何类型可以实现的一组功能的合同形式发布接口。基类可以随时间扩展。这些扩展成为每个派生类的一部分。两种模型可以混合使用,以在支持多个接口的同时重用实现代码。”

“与对基类类型进行编码相比,编码接口为其他开发人员提供了更大的灵活性。”

“使用接口为类定义API提供了更大的灵活性。”

“当您的类型将属性公开为类类型时,它会将整个接口公开给该类。使用接口,您可以选择仅公开希望客户端使用的方法和属性。”

“基类描述并实现了相关具体类型之间的常见行为。接口描述了不相关具体类型可以实现的功能的原子部分。两者都有其位置。类定义了您创建的类型。接口将这些类型的行为描述为功能块。如果您理解这些差异,那么您将创建更具表现力的设计,这些设计在面对变化时更具弹性。使用类层次结构来定义相关类型。使用跨这些类型实现的接口来公开功能。”


2
好吧,我阅读了它,并认为这是一个很好的答案。
埃里克·金

1
@ mc10不确定是什么问题。他引用了一本书,而答案的下半部分只是这本书的引文,如果您不想浪费时间,他会清楚地告诉您。
皮特

@Pete我不是说答案不好,只是说那太长了。我怀疑您有时需要整个报价。
kevinji 2011年

3
哈哈,如果这是tl; dr,我不确定您是否会喜欢这整个StackExchange-high-quality-answers。
Nic

哈哈,谢谢大家。是的,答案确实有些冗长,但我想我可以通过加入Wagner先生的相关引语来限定答案,该引语阐述接口与继承的优缺点,以及出现以下情况的场景:每种比较合适。也许直接引用这本书并不能给我的答案增加很多价值。
约翰·康纳利

8

尚未提及的一件事是测试:在所有C#模拟库以及Java中的某些库中,除非类实现了接口,否则无法模拟它们。这导致许多遵循敏捷/ TDD实践的项目每个类提供自己的界面。

有些人认为这是最佳做法,因为它“减少了耦合”,但我不同意-我认为这只是解决语言缺陷的一种解决方法。


我认为,当您有两个或多个类(抽象地以不同的方式执行“相同的事情”)时,最好使用接口。

例如,.NET框架有多个类,这些类的商店名单的东西,但他们都储存的东西以不同的方式。因此,有意义的是IList<T>可以使用不同的方法来实现抽象接口。

如果希望2个以上的类在将来可互换或可替换时,也应使用它。如果将来出现一种新的存储列表方式AwesomeList<T>,那么假设您IList<T>在整个代码中都使用过,那么将其更改为使用AwesomeList<T>将意味着仅更改几十行,而不是几百/千。


5

适当时不使用继承和接口的主要结果是紧密耦合。识别起来可能有点困难,但是通常最明显的症状是进行更改时,发现经常需要遍历其他文件来更改连锁反应。


4

不,这一点都不坏。当且仅当具有一个公共接口的两个类需要在运行时互换时,才应使用显式接口。如果它们不需要可互换,则不要让它们继承。就这么简单。继承是脆弱的,应尽可能避免。如果可以避免使用它,或者改用泛型,则可以这样做。

问题在于,在C#和Java等具有弱编译时泛型的语言中,您最终可能违反DRY,因为除非所有类都继承自相同的基类,否则无法编写可以处理多个类的方法。不过,C#4 dynamic 可以解决这个问题。

事实是,继承就像全局变量一样-一旦添加它并且您的代码依赖于它,上帝就会帮助您取消继承。但是,您可以随时添加它,甚至可以通过使用排序包装器添加它而无需更改基类。


1
否决的理由?
DeadMG

不确定,请投票。这是一个很好的答案。
Bryan Boettcher

3

是的,不是(或者很少)使用接口可能是一件坏事。接口(从抽象的意义上讲,C#/ Java语言构造是抽象意义上的一个很好的近似)定义了系统和子系统之间的显式交互点。这有助于减少耦合并使系统更易于维护。与改善可维护性的任何事情一样,系统越大越重要。


3

我好几年没有使用界面了。当然,这是因为多年来我几乎一直只使用Erlang进行编程,而接口的整个概念根本不存在。(获得的最接近的是一种“行为”,除非您非常用力地斜视着从眼角看去它们,否则这并不是真正的相同。)

因此,实际上,您的问题是与范式相关的(在这种情况下为OOP),而且实际上与语言有关(存在没有接口的OOP语言)。


2

如果您正在谈论使用Java,那么使用接口的原因之一是它们可以在不使用代码生成库的情况下启用代理对象。当您使用像Spring这样的复杂框架时,这可能是一个很大的优势。此外,某些功能需要接口:RMI是经典的示例,因为您必须按照接口(继承自java.rmi.Remote)描述要提供的功能,但是您要实现它们。


1

事情正在发生变化(MS Moles),但是我认为几乎只对接口进行编码是件好事的主要原因是它们易于模拟并且可以自然地适合IoC架构。

IMO,您应该只使用接口,或者在任何可能的情况下完全哑巴DAO。一旦您进入了这种思维定势,并且开始使用一个不会通过接口公开自身并通过具体对象来完成所有事情的库,那么我确实感到有些笨拙。


0

对于老式项目,人们使用接口来避免循环引用,因为从长远来看,循环引用可能会成为巨大的维护问题。

坏:

class B; // forward declaration
class A
{
  B* b;
};

class B
{
  A* a;
}

不错:

class PartOfClassB_AccessedByA
{
};

class A
{
  PartOfClassB_AccessedByA* b;
};

class B : public PartOfClassB_AccessedByA
{
  A* a;
}

通常使用单独的文件实现A,B,PartOfClassB_AccessedByA。


0

基于接口的编程有助于使代码更灵活且更易于测试。无需触摸客户端代码即可更改实现类[灵活性]。测试代码可以代替实际的基于oInterface的编程,这有助于使代码更灵活,更易于测试。无需触摸客户端代码即可更改实现类[灵活性]。在测试代​​码时,可以用模拟对象[Testability]替换实际对象。用模拟对象[Testability]替换对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.