我曾多次看到有人提到这一点,但我不清楚这是什么意思。你什么时候,为什么要这么做?

我知道接口是做什么的,但我不清楚这一点的事实使我认为我错过了正确使用它们。

如果你要这样做

IInterface classRef = new ObjectWhatever()

你可以使用任何实现IInterface的类吗?你什么时候需要这样做?我能想到的唯一一件事是,如果你有一个方法,你不确定什么对象将被传递,除了它实现IInterface。我不知道你需要多久做一次。

另外,如何编写一个方法来接受实现接口的对象呢?这可能吗?


当前回答

你应该看看反转控制:

Martin Fowler:控制反转容器和依赖注入模式 维基百科:控制反转

在这种情况下,你不会这样写:

IInterface classRef = new ObjectWhatever();

你可以这样写:

IInterface classRef = container.Resolve<IInterface>();

这将进入容器对象中基于规则的设置,并为您构造实际对象,该对象可以是ObjectWhatever。重要的是,您可以将此规则替换为完全使用另一种类型对象的规则,而您的代码仍然可以工作。

如果我们不考虑IoC,那么您可以编写的代码知道它可以与执行特定操作的对象进行对话,但不知道是哪种类型的对象或它如何执行操作。

这将在传递参数时派上用场。

至于你带圆括号的问题“另外,你如何编写一个方法来接受一个实现接口的对象?”这可能吗?”,在c#中,你可以简单地使用接口类型作为参数类型,如下所示:

public void DoSomethingToAnObject(IInterface whatever) { ... }

这直接插入到“与做特定事情的对象对话”中。上面定义的方法知道从对象中期望得到什么,它实现了IInterface中的所有内容,但它并不关心它是哪种类型的对象,只关心它遵守契约,这就是接口。

例如,你可能对计算器很熟悉,也可能在你的生活中使用过不少计算器,但大多数时候它们都是不同的。另一方面,你知道标准计算器应该如何工作,所以你能够使用所有的计算器,即使你不能使用每个计算器都没有的特定功能。

这就是界面的美妙之处。你可以写一段代码,它知道它会得到传递给它的对象,它可以从这些对象中期待特定的行为。它并不关心它是什么类型的对象,只关心它支持所需的行为。

让我给你一个具体的例子。

我们为windows窗体定制了翻译系统。这个系统循环遍历表单上的控件,并翻译每个控件中的文本。这个系统知道如何处理基本的控件,比如拥有文本属性的控件类型,以及类似的基本东西,但对于任何基本的东西,它都做不到。

现在,由于控件继承自我们无法控制的预定义类,我们可以做以下三件事之一:

为我们的翻译系统提供支持,以检测它正在使用哪种类型的控件,并翻译正确的位(维护的噩梦) 将支持构建到基类中(不可能,因为所有控件都继承自不同的预定义类) 添加接口支持

所以是nr. 3。我们所有的控件都实现了ILocalizable,这是一个提供给我们一个方法的接口,能够将“本身”转换为翻译文本/规则的容器。因此,表单不需要知道它找到了哪种类型的控件,只需要知道它实现了特定的接口,并且知道可以调用某个方法来本地化该控件。

其他回答

我曾经给学生的一个具体例子是他们应该写作

List myList = new ArrayList(); // programming to the List interface

而不是

ArrayList myList = new ArrayList(); // this is bad

在一个短程序中,它们看起来完全相同,但如果在程序中继续使用myList 100次,就会开始看到区别。第一个声明确保只调用myList上由List接口定义的方法(因此没有ArrayList特定的方法)。如果您以这种方式对接口进行了编程,那么稍后您就可以确定您确实需要

List myList = new TreeList();

你只需要在这一点上修改代码。您已经知道,其余的代码不会因为更改实现而被破坏,因为您对接口进行了编程。

当您谈论方法参数和返回值时,好处甚至更明显(我认为)。举个例子:

public ArrayList doSomething(HashMap map);

该方法声明将您绑定到两个具体实现(ArrayList和HashMap)。一旦从其他代码调用该方法,对这些类型的任何更改都可能意味着您将不得不更改调用代码。最好是根据接口进行编程。

public List doSomething(Map map);

现在,不管您返回什么样的List,或者作为参数传入什么样的Map。在doSomething方法中所做的更改不会强制您更改调用代码。

你应该看看反转控制:

Martin Fowler:控制反转容器和依赖注入模式 维基百科:控制反转

在这种情况下,你不会这样写:

IInterface classRef = new ObjectWhatever();

你可以这样写:

IInterface classRef = container.Resolve<IInterface>();

这将进入容器对象中基于规则的设置,并为您构造实际对象,该对象可以是ObjectWhatever。重要的是,您可以将此规则替换为完全使用另一种类型对象的规则,而您的代码仍然可以工作。

如果我们不考虑IoC,那么您可以编写的代码知道它可以与执行特定操作的对象进行对话,但不知道是哪种类型的对象或它如何执行操作。

这将在传递参数时派上用场。

至于你带圆括号的问题“另外,你如何编写一个方法来接受一个实现接口的对象?”这可能吗?”,在c#中,你可以简单地使用接口类型作为参数类型,如下所示:

public void DoSomethingToAnObject(IInterface whatever) { ... }

这直接插入到“与做特定事情的对象对话”中。上面定义的方法知道从对象中期望得到什么,它实现了IInterface中的所有内容,但它并不关心它是哪种类型的对象,只关心它遵守契约,这就是接口。

例如,你可能对计算器很熟悉,也可能在你的生活中使用过不少计算器,但大多数时候它们都是不同的。另一方面,你知道标准计算器应该如何工作,所以你能够使用所有的计算器,即使你不能使用每个计算器都没有的特定功能。

这就是界面的美妙之处。你可以写一段代码,它知道它会得到传递给它的对象,它可以从这些对象中期待特定的行为。它并不关心它是什么类型的对象,只关心它支持所需的行为。

让我给你一个具体的例子。

我们为windows窗体定制了翻译系统。这个系统循环遍历表单上的控件,并翻译每个控件中的文本。这个系统知道如何处理基本的控件,比如拥有文本属性的控件类型,以及类似的基本东西,但对于任何基本的东西,它都做不到。

现在,由于控件继承自我们无法控制的预定义类,我们可以做以下三件事之一:

为我们的翻译系统提供支持,以检测它正在使用哪种类型的控件,并翻译正确的位(维护的噩梦) 将支持构建到基类中(不可能,因为所有控件都继承自不同的预定义类) 添加接口支持

所以是nr. 3。我们所有的控件都实现了ILocalizable,这是一个提供给我们一个方法的接口,能够将“本身”转换为翻译文本/规则的容器。因此,表单不需要知道它找到了哪种类型的控件,只需要知道它实现了特定的接口,并且知道可以调用某个方法来本地化该控件。

有很多解释,但让它更简单。以List为例。可以使用as实现一个列表:

内部数组 链表 其他实现

通过构建一个接口,比如一个List。您只编写了List的定义或List在实际中的含义。

你可以在内部使用任何类型的实现比如数组实现。但是假设您希望由于某种原因(比如bug或性能)更改实现。然后你只需要改变声明List<String> ls = new ArrayList<String>()为List<String> ls = new LinkedList<String>()。

在代码的其他地方,你不需要改变任何东西;因为其他所有东西都建立在List的定义之上。

听起来好像你知道接口是如何工作的,但不确定何时使用它们以及它们提供了什么优势。下面是几个例子,说明界面是有意义的:

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

然后我可以创建GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider等。

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

然后创建JpegImageLoader, GifImageLoader, PngImageLoader等。

大多数外接程序和插件系统都离不开接口。

另一种流行的用法是Repository模式。假设我想从不同的来源加载一个邮政编码列表

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

然后我可以创建一个XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository等。对于我的web应用程序,我经常在早期创建XML存储库,这样我就可以在SQL数据库准备好之前启动并运行一些东西。一旦数据库准备好了,我就编写一个SQLRepository来替换XML版本。我的其余代码保持不变,因为它完全基于接口运行。

方法可以接受如下接口:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

短篇故事:一个邮递员被要求挨家挨户地接收写有地址的信封(信件、文件、支票、礼品卡、申请书、情书)。

假设没有封面,让邮递员挨家挨户地接收所有的东西,然后交给其他人,邮递员会感到困惑。

所以最好用封面(在我们的故事中是接口)包装它,然后他就会很好地完成他的工作。

现在邮差的工作只是收和送封皮(他不关心封皮里面有什么)。

创建一个接口类型,而不是实际类型,而是使用实际类型实现它。

创建一个接口意味着你的组件可以很容易地适应其余的代码

我给你们举个例子。

你有一个plane界面,如下所示。

interface Airplane{
    parkPlane();
    servicePlane();
}

假设在平面的Controller类中有这样的方法

parkPlane(Airplane plane)

and

servicePlane(Airplane plane)

在您的程序中实现。它不会破坏你的代码。 我的意思是,只要它接受参数plane,它就不需要改变。

因为它将接受任何飞机,不管实际类型,飞行员,高空飞行,战斗机等。

同样,在集合中:

列表> <飞机飞机;//会占用你所有的飞机。

下面的例子将帮助您理解。


你有一架战斗机来执行它,所以

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

HighFlyer和其他类也是如此:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

现在假设你的控制器类多次使用AirPlane,

假设你的控制器类是ControlPlane,如下所示,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

神奇的是,你可以在不改变ControlPlane类代码的情况下,尽可能多地创建新的AirPlane类型实例。

你可以添加一个实例…

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

您也可以删除以前创建的类型的实例。