在我的开发学习中,我觉得我必须学习更多关于接口的知识。
我经常读到它们,但我似乎无法理解它们。
我读过这样的例子:动物基类,IAnimal接口的东西,如“Walk”,“Run”,“GetLegs”等-但我从来没有工作过,觉得“嘿,我应该在这里使用接口!”
我错过了什么?为什么这个概念对我来说这么难理解!我只是害怕这样一个事实,我可能从来没有意识到一个具体的需要-主要是由于一些缺失的理解他们!这让我觉得我作为一名开发人员缺少了一些东西!如果有人有过这样的经历,并取得了突破,我会很感激一些关于如何理解这个概念的建议。谢谢你!
与任何为系统增加灵活性的编程技术一样,接口也增加了一定程度的复杂性。它们通常都很好,而且你可以在任何地方使用它(你可以为你的所有类创建一个接口)——但是这样做,你会创建一个更复杂的系统,更难维护。
像往常一样,这里有一个权衡:灵活性胜过可维护性。哪个更重要?没有答案——这取决于项目本身。但是请记住,每个软件都需要维护……
所以我的建议是:在真正需要接口之前不要使用它们。(使用Visual Studio,你可以在2秒内从现有的类中提取一个接口——所以不要着急。)
话虽如此,你什么时候需要创建一个接口呢?
当我重构一个突然需要处理两个或多个类似类的方法时,我就会这样做。然后创建一个接口,将该接口分配给两个(或多个)类似的类,并更改方法参数类型(将类类型替换为接口类型)。
它是有效的:o)
一个例外:当我模拟对象时,接口更容易使用。我经常为此创建接口。
PS:当我写“接口”时,我的意思是:“任何基类的接口”,包括纯接口类。请注意,抽象类通常比纯接口更好,因为您可以向它们添加逻辑。
此致,Sylvain。
一个代码示例(结合了Andrew的代码和我的额外的关于“接口的目的是什么”的代码),也说明了为什么在不支持多重继承的语言(c#和java)上接口而不是抽象类:
interface ILogger
{
void Log();
}
class FileLogger : ILogger
{
public void Log() { }
}
class DataBaseLogger : ILogger
{
public void Log() { }
}
public class MySpecialLogger : SpecialLoggerBase, ILogger
{
public void Log() { }
}
注意,FileLogger和DataBaseLogger不需要接口(可以是Logger的抽象基类)。但是考虑到您需要使用第三方记录器,它迫使您使用基类(假设它公开了您需要使用的受保护的方法)。由于语言不支持多重继承,您将无法使用抽象基类方法。
底线是:尽可能使用接口来获得代码的额外灵活性。您的实现不那么受约束,因此它能更好地适应变化。
一些非编程示例可能帮助您了解接口在编程中的适当使用。
There's an interface between electrical devices and the electricity network - it's the set of conventions about the shape of the plugs and sockets and the voltages/currents across them. If you want to implement a new electrical device, as long as your plug follows the rules it will be able to get services from the network. This makes extensibility very easy and removes or lowers the costs of coordination: you don't have to notify the electricity provider about how your new device works and come to a separate agreement about how to plug your new device into the network.
各国都有标准的铁路轨距。这使得铺设铁轨的工程公司和建造在这些铁轨上运行的列车的工程公司之间实现了分工,并使铁路公司有可能在不重新设计整个系统的情况下更换和升级列车。
The service a business presents to a client can be described as an interface: a well defined interface emphasises the service and hides the means. When you put a letter in a mailbox, you expect the postal system to deliver the letter within a given time but you have no expectations about how the letter is delivered: you don't need to know, and the postal service has the flexibility to choose the means of delivery that best meets the requirements and current circumstances. An exception to this is the ability of customers to choose airmail - that's not the kind of interface a modern computer programmer would have designed, since it reveals too much of the implementation.
来自自然的例子:我不太喜欢eats(), makesSound(), moves()等例子。它们确实描述了行为,这是正确的,但它们没有描述交互以及它们是如何被启用的。在自然界中,使相互作用成为可能的界面的一个明显的例子是与繁殖有关的,例如一朵花为蜜蜂提供了一个特定的界面,以便授粉能够发生。
把接口想象成一个契约。这是一种说法,“这些类应该遵循这些规则。”
所以在IAnimal的例子中,它是一种说,“我必须能够在实现IAnimal的类上调用Run, Walk等。”
为什么这个有用?您可能希望构建一个函数,该函数依赖于必须能够在对象上调用Run和Walk这一事实。你可以有以下内容:
public void RunThenWalk(Monkey m) {
m.Run();
m.Walk();
}
public void RunThenWalk(Dog d) {
d.Run();
d.Walk();
}
... 对所有你知道能跑能走的物体重复这一步骤。然而,在你的IAnimal接口中,你可以像下面这样定义函数:
public void RunThenWalk(IAnimal a) {
a.Run();
a.Walk();
}
通过根据接口编程,您实际上是信任类来实现接口的目的。所以在我们的例子中,想法是“我不在乎他们怎么跑和走,只要他们能跑和走。”只要他们履行协议,我的RunThenWalk就有效。它在不了解任何其他课程的情况下运行得很好。”
在这个相关的问题上也有很好的讨论。
我偶尔也会使用接口,下面是我最新的用法(名称已经概括了):
我在WinForm上有一堆需要将数据保存到业务对象的自定义控件。一种方法是分别调用每个控件:
myBusinessObject.Save(controlA.Data);
myBusinessObject.Save(controlB.Data);
myBusinessObject.Save(controlC.Data);
这个实现的问题是,每当我添加一个控件,我必须进入我的“保存数据”方法,并添加新的控件。
我改变了我的控件来实现一个ISaveable接口,它有一个方法SaveToBusinessObject(…),所以现在我的“保存数据”方法只是通过控件迭代,如果它发现一个是ISaveable,它调用SaveToBusinessObject。所以现在当需要一个新的控件时,所有人要做的就是在该对象中实现ISaveable(并且永远不要触及其他类)。
foreach(Control c in Controls)
{
ISaveable s = c as ISaveable;
if( s != null )
s.SaveToBusinessObject(myBusinessObject);
}
接口通常未被意识到的好处是本地化修改。定义之后,您很少会更改应用程序的整体流程,但通常会在细节级别上进行更改。当您将细节保存在特定对象中时,ProcessA中的更改将不会影响ProcessB中的更改。(基类也有这个好处。)
编辑:另一个好处是行动的专一性。就像在我的例子中,我所要做的就是保存数据;我不关心它是什么类型的控件,或者它是否可以做任何其他事情——我只想知道我是否可以保存控件中的数据。它使我的保存代码非常清晰——没有检查它是否为文本、数字、布尔值或任何东西,因为自定义控件处理所有这些。
它解决了一个具体的问题:
你有a b c d四种不同类型。在你的代码中,你可以这样写:
a.Process();
b.Process();
c.Process();
d.Process();
为什么不让他们实现IProcessable呢
List<IProcessable> list;
foreach(IProcessable p in list)
p.Process();
当你添加50种类型的类,它们都做同样的事情时,这种伸缩性会更好。
另一个具体问题是:
你有没有看过System.Linq.Enumerable?它定义了大量的扩展方法,可以对实现IEnumerable的任何类型进行操作。因为任何实现IEnumerable的东西基本上都在说“我支持无序foreach类型模式中的迭代”,所以你可以为任何可枚举类型定义复杂的行为(Count、Max、Where、Select等)。