“编程到接口”是什么意思?

我已经看到这个提到了几次,我不清楚它是什么意思。你什么时候和为什么要这样做?

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

如果你要这样做,是不是就这样了:

IInterface classRef = new ObjectWhatever()

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

另外,你怎么能编写一个接受实现接口的对象的方法?这可能吗?

213701 次浏览

你应该看看控制倒置:

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

IInterface classRef = new ObjectWhatever();

你会写这样的东西:

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

这将进入container对象中基于规则的设置,并为您构造实际对象,它可以是Object无论如何。重要的是,您可以将此规则替换为完全使用另一种类型对象的东西,并且您的代码仍然可以工作。

如果我们将IoC排除在外,您可以编写知道它可以与对象做一些具体的事情对话的代码,但不知道它可以与哪种类型的对象或如何对话。

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

至于你的括号问题“还有,你怎么能写一个接受实现接口的对象的方法?这可能吗?”,在C#中,你只需将接口类型用于参数类型,如下所示:

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

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

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

这就是接口的美妙之处。你可以编写一段代码,它知道它将获得传递给它的对象,它可以期望从中获得某些行为。它不关心它是什么样的对象,只关心它支持所需的行为。

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

我们有一个为windows窗体定制的翻译系统。这个系统循环遍历窗体上的控件并翻译每个控件中的文本。该系统知道如何处理基本控件,例如控件的类型以及类似的基本内容,但对于任何基本内容,它都不够。

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

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

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

编程到一个接口是说,“我需要这个功能,我不在乎它来自哪里。

考虑(在Java)List接口与ArrayListLinkedList具体类。如果我关心的只是我有一个包含多个数据项的数据结构,我应该通过迭代访问,我会选择List(这是99%的时间)。如果我知道我需要从列表的两端恒定时间插入/删除,我可能会选择LinkedList具体实现(或者更有可能,使用队列接口)。如果我知道我需要通过索引随机访问,我会选择ArrayList具体类。

当你有一组类似的类时,它使你的代码更具可扩展性,更容易维护。我是一名初级程序员,所以我不是专家,但我刚刚完成了一个需要类似东西的项目。

我从事与运行医疗设备的服务器通信的客户端软件工作。我们正在开发该设备的新版本,其中包含一些客户有时必须配置的新组件。有两种类型的新组件,它们是不同的,但它们也非常相似。基本上,我必须创建两个配置表单,两个列表类,两个一切。

我决定最好为每个控件类型创建一个抽象基类,它将包含几乎所有的实际逻辑,然后创建派生类型来处理两个组件之间的差异。然而,如果我不得不一直担心类型,基类将无法对这些组件执行操作(嗯,他们可以,但每个方法中都会有一个“if”语句或开关)。

我为这些组件定义了一个简单的接口,所有的基类都与这个接口通信。现在,当我改变一些东西时,它几乎在任何地方都可以工作,我没有代码重复。

除了现有的帖子之外,当开发人员同时处理不同的组件时,有时对接口的编码对大型项目有帮助。你需要的只是预先定义接口并为它们编写代码,而其他开发人员为你正在实现的接口编写代码。

除了消除类之间不必要的耦合之外,使用接口是使代码易于测试的关键因素。通过创建定义类操作的接口,您允许想要使用该功能的类使用它,而不直接依赖于您的实现类。如果稍后您决定更改并使用不同的实现,您只需要更改代码中实例化实现的部分。代码的其余部分不需要更改,因为它取决于接口,而不是实现类。

这在创建单元测试时非常有用。在被测类中,你让它依赖接口,并通过构造函数或属性设置函数将接口实例注入类(或允许它根据需要构建接口实例的工厂)。该类在其方法中使用提供(或创建)的接口。当你去编写测试时,你可以模拟或伪造接口,并提供一个响应单元测试中配置的数据的接口。你可以这样做,因为你的被测类只处理接口,而不是你的具体实现。任何实现接口的类,包括你的模拟或伪造类,都会这样做。

编辑:下面是一篇文章的链接,其中Erich Gamma讨论了他的引用,“编程到接口,而不是实现”。

http://www.artima.com/lejava/articles/designprinciples.html

如果你用Java编程,JDBC就是一个很好的例子。JDBC定义了一组接口,但没有说明实现。你的应用程序可以针对这组接口编写。理论上,你选择一些JDBC驱动程序,你的应用程序就会正常工作。如果你发现有更快、“更好”或更便宜的JDBC驱动程序,或者出于某种原因,理论上你可以再次重新配置你的属性文件,而无需对你的应用程序进行任何更改,你的应用程序仍然可以工作。

我过去给学生举的具体例子是,他们应该写

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);

该方法声明将您与两个具体实现(ArrayListHashMap)联系起来。一旦从其他代码调用该方法,对这些类型的任何更改都可能意味着您也必须更改调用代码。最好针对接口进行编程。

public List doSomething(Map map);

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

假设您有一个名为“Zebra”的产品,可以通过插件进行扩展。它通过在某个目录中搜索DLL来找到插件。它加载所有这些DLL并使用反射来查找实现IZebraPlugin的任何类,然后调用该接口的方法与插件通信。

这使得它完全独立于任何特定的插件类-它不关心类是什么。它只关心它们是否满足接口规范。

接口是定义可扩展性点的一种方式。与接口对话的代码更松耦合——事实上,它根本不耦合到任何其他特定代码。它可以与多年后编写的插件进行互操作,这些插件是由从未见过原始开发人员的人编写的。

您可以使用带有虚函数的基类-所有插件都将从基类派生。但这更具局限性,因为一个类只能有一个基类,而它可以实现任意数量的接口。

接口编程很棒,它促进了松耦合。正如@lassevk提到的,控制反转是一个很好的用途。

此外,查看SOLID原则.这是一个视频系列

它经过硬编码(强耦合示例),然后查看接口,最后进入IoC/DI工具(NInject)

这里有一些关于这个问题的精彩答案,它们涉及到关于接口、松散耦合代码、控制反转等的各种细节。有一些相当令人兴奋的讨论,所以我想借此机会分解一下,以理解为什么接口是有用的。

当我第一次接触接口时,我也对它们的相关性感到困惑。我不明白为什么需要它们。如果我们使用的是Java或C#之类的语言,我们已经有了继承,我将接口视为形式的继承,并想,“为什么要麻烦呢?”从某种意义上说,我是对的,你可以将接口视为一种弱形式的继承,但除此之外,我最终理解了它们作为一种语言构造的用途,将它们视为一种分类常见特征或行为的手段,这些特征或行为可能由许多不相关的对象类表现出来。

例如,假设你有一个SIM游戏,并且有以下类:

class HouseFly inherits Insect {void FlyAroundYourHead(){}void LandOnThings(){}}
class Telemarketer inherits Person {void CallDuringDinner(){}void ContinueTalkingWhenYouSayNo(){}}

显然,这两个对象在直接继承方面没有任何共同点。但是,你可以说它们都很烦人。

假设我们的游戏需要有某种随机的事情,当玩家吃晚餐时会惹恼他们。这可能是HouseFlyTelemarketer,或者两者兼而有之——但你如何通过一个函数允许两者兼而有之?你如何让每种不同类型的对象以同样的方式“做他们讨厌的事情”?

要实现的关键是TelemarketerHouseFly都有一个共同的松散解释行为,即使它们在建模方面完全不同。所以,让我们做一个两者都可以实现的接口:

interface IPest {void BeAnnoying();}
class HouseFly inherits Insect implements IPest {void FlyAroundYourHead(){}void LandOnThings(){}
void BeAnnoying() {FlyAroundYourHead();LandOnThings();}}
class Telemarketer inherits Person implements IPest {void CallDuringDinner(){}void ContinueTalkingWhenYouSayNo(){}
void BeAnnoying() {CallDuringDinner();ContinueTalkingWhenYouSayNo();}}

我们现在有两个类,每个类都有自己的烦恼。它们不需要派生自相同的基类并共享共同的固有特征-它们只需要满足IPest的契约-该契约很简单。你只需要BeAnnoying。在这方面,我们可以对以下内容进行建模:

class DiningRoom {
DiningRoom(Person[] diningPeople, IPest[] pests) { ... }
void ServeDinner() {when diningPeople are eating,
foreach pest in pestspest.BeAnnoying();}}

在这里,我们有一个餐厅,可以容纳许多食客和一些害虫-注意界面的使用。这意味着在我们的小世界中,pests数组的成员实际上可以是Telemarketer对象或HouseFly对象。

ServeDinner方法是在晚餐准备好了,餐厅里的人应该吃饭的时候调用的。在我们的小游戏中,那是我们的害虫做它们的工作的时候——每个害虫都被指示通过IPest界面变得烦人。通过这种方式,我们可以很容易地让TelemarketersHouseFlys以各自的方式变得烦人——我们只关心DiningRoom对象中有什么是害虫,我们并不真正关心它是什么,它们可能与其他对象没有任何共同之处。

这个做作的伪代码示例(拖了比我预期的要长很多)只是为了说明那种最终让我明白什么时候可以使用接口的东西。对于这个例子的愚蠢,我提前道歉,但希望它能帮助你理解。而且,可以肯定的是,你在这里收到的其他发布的答案真的涵盖了当今设计模式和开发方法学中接口使用的范围。

Java这些具体类都实现了CharSequence接口:

CharBuffer, String, StringBuffer, StringBuilder

这些具体类除了Object之外没有一个共同的父类,所以除了它们每个都与字符数组、表示或操作字符数组有关之外,没有任何联系。例如,一旦String对象被实例化,String的字符就不能更改,而StringBuffer或StringBuilder的字符可以编辑。

然而,这些类中的每一个都能够适当地实现CharSequence接口方法:

char charAt(int index)int length()CharSequence subSequence(int start, int end)String toString()

在某些情况下,过去接受String的Java类库类已经被修改为现在接受CharSequence接口。因此,如果您有StringBuilder的实例,而不是提取String对象(这意味着实例化一个新的对象实例),它可以在实现CharSequence接口时仅传递StringBuilder本身。

一些类实现的Appendable接口对于任何可以将字符附加到底层具体类对象实例的实例的情况都有类似的好处。所有这些具体类都实现了Appendable接口:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, Output StreamWriter, PipeedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer

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

// 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等。

大多数外接程序和插件系统都是通过接口工作的。

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

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

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

方法可以接受接口,例如:

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

所以,为了正确理解这一点,接口的优点是我可以将方法的调用与任何特定的类分开。相反,创建一个接口的实例,其中实现来自我选择的实现该接口的任何类。因此,允许我有许多类,它们具有相似但略有不同的功能,在某些情况下(与接口意图相关的情况)不在乎它是哪个对象。

例如,我可以有一个移动接口。一个使某物“移动”的方法和任何实现移动接口的对象(Person、Car、Cat)都可以传入并被告知移动。如果没有方法,每个人都知道它是什么类型的类。

它对单元测试也有好处,你可以将自己的类(满足接口要求的)注入到依赖它的类中

如果我正在编写一个新的类Swimmer来添加功能swim(),并且需要使用类的对象,例如Dog,并且这个Dog类实现了声明swim()的接口Animal

在层次结构的顶部(Animal),它非常抽象,而在底部(Dog)它非常具体。我对“接口编程”的思考是,当我编写Swimmer类时,我想针对层次结构中最远的接口编写代码,在这种情况下,接口是Animal对象。接口没有实现细节,因此使你的代码松耦合。

实现细节可以随着时间的推移而改变,但是,它不会影响剩余的代码,因为您与之交互的只是接口而不是实现。你不在乎实现是什么样的…你只知道会有一个类来实现接口。

对接口进行编程与我们在Java或. NET中看到的抽象接口完全无关。它甚至不是一个面向对象的概念。

这意味着不要搞乱对象或数据结构的内部结构。使用抽象程序接口或API与您的数据交互。在Java或C#中,这意味着使用公共属性和方法而不是原始字段访问。对于C,这意味着使用函数而不是原始指针。

编辑:对于数据库,这意味着使用视图和存储过程而不是直接访问表。

C++解释。

将接口视为您的类公共方法。

然后你可以创建一个依赖于这些公共方法的模板来执行它自己的函数(它使函数调用在类的公共接口中定义)。假设这个模板是一个容器,就像一个Vector类,它依赖的接口是一个搜索算法。

任何定义Vector调用的函数/接口的算法类都将满足“契约”(正如有人在原始回复中解释的那样)。算法甚至不需要是相同的基类;唯一的要求是Vector依赖的函数/方法(接口)在你的算法中定义。

所有这一切的重点是,您可以提供任何不同的搜索算法/类,只要它提供了Vector依赖的接口(气泡搜索、顺序搜索、快速搜索)。

您可能还想设计其他容器(列表、队列),这些容器将利用与Vector相同的搜索算法,让它们满足搜索算法所依赖的接口/契约。

这节省了时间(OOP原则“代码重用”),因为您可以一次编写算法,而不是一次又一次地针对您创建的每个新对象编写算法,而不会使过度生长的继承树问题过于复杂。

至于“错过”事物如何运作;大时间(至少在C++),因为这是大多数标准模板库框架的运作方式。

当然,当使用继承和抽象类时,接口编程的方法论会发生变化;但原则是一样的,你的公共函数/方法就是你的类接口。

这是一个巨大的话题,也是设计模式的基石原则之一。

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

  1. 一个内部阵列
  2. 一个链表
  3. 其他实现

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

您可以在内部使用任何类型的实现,例如array实现。但是假设您出于某种原因希望更改实现,例如bug或性能。那么您只需将声明List<String> ls = new ArrayList<String>()更改为List<String> ls = new LinkedList<String>()

在代码中没有其他地方,你必须改变其他任何东西;因为其他一切都是建立在List的定义之上的。

Q:-…"你可以使用任何实现接口的类吗?"
答:是的。

问:“你什么时候需要这样做?”
答:-每次你需要一个实现接口的类。

备注:我们不能实例化一个不是由类实现的接口-真的。

  • 为啥?
  • 因为接口只有方法原型,没有定义(只有函数名,没有它们的逻辑)

AnIntf anInst = new Aclass();
//0
//anInst将有Aclass引用。


备注:现在我们可以理解如果Bclass和Cclass实现了相同的Dintf会发生什么。

Dintf bInst = new Bclass();// now we could call all Dintf functions implemented (defined) in Bclass.
Dintf cInst = new Cclass();// now we could call all Dintf functions implemented (defined) in Cclass.

我们所拥有的:相同的接口原型(接口中的函数名称),并调用不同的实现。

参考书目:原型-维基百科

小故事:一个邮递员被要求回家后,收到封面包含(信件、文件、支票、礼品卡、申请、情书),上面写着要投递的地址。

假设没有掩护,要求邮递员回家后收到所有的东西并交付给其他人,邮递员可能会感到困惑。

所以最好用封面包装它(在我们的故事中是界面),然后他会做得很好。

现在邮递员的工作是接收和发送封面(他不会打扰封面里面的东西)。

创建一个类型interface不是实际类型,但使用实际类型实现它。

创建到接口意味着您的组件获得轻松适应其余代码

我举个例子。

你有如下的AirPlane界面。

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

假设你的Controller类中有一些方法,比如

parkPlane(Airplane plane)

servicePlane(Airplane plane)

在您的程序中实现。它不会休息您的代码。我的意思是,只要它接受参数为AirPlane,它就不需要改变。

因为它将接受任何飞机,尽管实际类型,flyerhighflyrfighter等。

此外,在集合中:

List<Airplane> 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

假设你的Controller类是ControlPlane,如下所示,

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

这里的魔法来了,因为你可以让你的新AirPlane类型实例尽可能多,你不改变ControlPlane类的代码。

您可以添加实例…

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

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

我在这里也看到了很多很好的解释性答案,所以我想在这里给出我的观点,包括一些我在使用这种方法时注意到的额外信息。

单元测试

在过去的两年里,我写了一个业余爱好项目,我没有为它写单元测试。在写了50K行之后,我发现写单元测试真的很有必要。我没有使用接口(或很少使用)……当我做第一个单元测试时,我发现它很复杂。为什么?

因为我必须制作很多类实例,用作类变量和/或参数的输入。所以测试看起来更像是集成测试(必须制作一个完整的类“框架”,因为所有类都绑在一起)。

对接口的恐惧所以我决定使用接口。我担心我必须多次在任何地方(在所有使用的类中)实现所有功能。在某种程度上,这是真的,然而,通过使用继承,它可以减少很多。

接口与继承的结合我发现这个组合非常适合使用。我举了一个非常简单的例子。

public interface IPricable{int Price { get; }}
public interface ICar : IPricable
public abstract class Article{public int Price { get { return ... } }}
public class Car : Article, ICar{// Price does not need to be defined here}

这种方式不需要复制代码,同时仍然具有使用汽车作为接口(ICar)的好处。

这里有一个简单的例子来说明当你编程的航班预订系统。

//This interface is very flexible and abstractaddPassenger(Plane seat, Ticket ticket);
//Boeing is implementation of PlaneaddPassenger(Boeing747 seat, EconomyTicket ticket);addPassenger(Cessna, BusinessClass ticket);

addPassenger(J15, E87687);

让我们先从一些定义开始:

接口n.由对象操作定义的所有签名的集合称为对象的接口

类型n.特定接口

上面定义的接口的一个简单示例是所有PDO对象方法,例如query()commit()close()等,作为一个整体,而不是单独的。这些方法,即其接口定义了可以发送到对象的完整消息集、请求。

上面定义的类型是一个特定的接口。我将使用化妆形状接口来演示:draw()getArea()getPerimeter()等。

如果一个对象是数据库类型的,我们的意思是它接受数据库接口的消息/请求,query()commit()等。对象可以有多种类型。只要它实现了它的接口,你可以让数据库对象成为形状类型,在这种情况下,这将是分型

许多对象可以有许多不同的接口/类型,并以不同的方式实现该接口。这允许我们替换对象,让我们选择使用哪个。也称为多态。

客户端将只知道接口而不知道实现。

因此,从本质上讲,对接口进行编程将涉及制作某种类型的抽象类,例如Shape,仅指定接口,即draw()getCoordinates()getArea()等…然后让不同的具体类实现这些接口,例如Circle类,Square类,Triangle类。

对于这个问题,我是一个迟到的人,但我想在这里提到,“Program to an接口,not an实现”这行在GoF(四人组)Design Patterns一书中进行了一些很好的讨论。

它在第18页指出:

编程到接口,而不是实现

不要将变量声明为特定具体类的实例。相反,只提交由抽象类定义的接口。你会发现这是本书设计模式的共同主题。

除此之外,它开始于:

仅根据抽象类定义的接口来操作对象有两个好处:

  1. 客户端仍然不知道他们使用的特定类型的对象,只要对象坚持客户端期望的接口。
  2. 客户端仍然不知道实现这些对象的类。客户端只知道定义接口的抽象类。

所以换句话说,不要把它写成你的类,这样它就有了鸭子的quack()方法,然后又有了狗的bark()方法,因为它们对于类(或子类)的特定实现来说太具体了。相反,使用足够通用的名称来编写方法,以便在基类中使用,例如giveSound()move(),这样它们就可以用于鸭子、狗甚至汽车,然后类的客户端可以只说.giveSound(),而不是考虑是使用quack()还是bark(),甚至在发出要发送给对象的正确消息之前确定类型。

接口的代码不是实现与Java无关,也不是它的接口构造。

这个概念在《模式/四人帮》一书中得到了突出,但很可能在此之前就已经存在了。

Java接口构造是为了帮助实现这个想法而创建的(除其他外),人们过于关注构造作为意义的中心而不是原始意图。然而,这就是我们在Java、C++、C#等中拥有公共和私有方法和属性的原因。

它的意思是只与对象或系统的公共接口交互。不要担心甚至预测它在内部是如何做的。不要担心它是如何实现的。在面向对象的代码中,这就是为什么我们有公共和私有方法/属性。我们打算使用公共方法,因为私有方法仅供内部使用,在类内。它们组成了类的实现,可以根据需要进行更改,而无需更改公共接口。假设就功能而言,每次你用相同的参数调用类上的方法时,它将执行相同的操作,具有相同的预期结果。它允许作者改变类的工作方式,它的实现,而不会破坏人们与它的交互方式。

您可以对接口进行编程,而不是实现,而无需使用接口构造。你可以针对接口而不是C++中的实现进行编程,因为C++没有接口构造。你可以更健壮地集成两个庞大的企业系统,只要它们通过公共接口(合约)交互,而不是调用系统内部对象的方法。给定相同的输入参数,接口应该总是以相同的预期方式做出反应;如果实现到接口而不是实现。这个概念在很多地方都有效。

不要认为Java接口与“编程到接口,而不是实现”的概念有任何关系。它们可以帮助应用这个概念,但它们是0号概念。

接口就像一个契约,你希望你的实现类实现契约(接口)中编写的方法。由于Java不提供多重继承,“编程到接口”是实现多重继承的好方法。

如果你有一个已经扩展了其他类B的类A,但你希望该类A也遵循某些准则或实现某个契约,那么你可以通过“编程到接口”策略来做到这一点。

程序到接口允许无缝更改由接口定义的契约的实现。它允许契约和特定实现之间的松散耦合。

IInterface classRef = new ObjectWhatever()

你可以使用任何实现IInterface的类?你什么时候需要这样做?

看看这个SE问题的一个很好的例子。

为什么要首选Java类的接口?

使用接口会影响性能吗?

如果是,多少?

是的。它会在亚秒内产生轻微的性能开销。但是如果您的应用程序需要动态更改接口的实现,请不要担心性能影响。

如何在不维护两位代码的情况下避免它?

如果你的应用程序需要接口的多个实现,不要试图避免它们。在接口与一个特定实现没有紧密耦合的情况下,你可能必须部署补丁来将一个实现更改为另一个实现。

一个很好的用例:策略模式的实现:

策略模式的真实世界示例

对接口进行编程可能是有利的,即使我们不依赖抽象。

编程到接口强制我们使用对象的上下文适当的子集。这很有帮助,因为它:

  1. 阻止我们做不恰当的事情
  2. 让我们在将来安全地更改实现。

例如,考虑一个实现FriendEmployee接口的Person类。

class Person implements AbstractEmployee, AbstractFriend {}

在人的生日的上下文中,我们编程到Friend接口,以防止将人视为Employee

function party() {const friend: Friend = new Person("Kathryn");friend.HaveFun();}

在个人工作的背景下,我们对Employee界面进行编程,以防止模糊工作场所的界限。

function workplace() {const employee: Employee = new Person("Kathryn");employee.DoWork();}

太好了。我们在不同的环境中表现得很好,我们的软件运行良好。

在遥远的未来,如果我们的业务改变为与狗一起工作,我们可以相当容易地更改软件。首先,我们创建一个Dog类,实现FriendEmployee。然后,我们安全地将new Person()更改为new Dog()。即使这两个函数都有数千行代码,简单的编辑也会起作用,因为我们知道以下是正确的:

  1. 函数party仅使用PersonFriend子集。
  2. 函数workplace仅使用PersonEmployee子集。
  3. Dog实现了FriendEmployee接口。

另一方面,如果partyworkplace针对Person进行了编程,那么两者都有特定于Person的代码的风险。从Person更改为Dog将需要我们梳理代码以删除任何Dog不支持的特定于Person的代码。

的道德:接口编程有助于我们的代码行为得当并为更改做好准备。它还使我们的代码准备好依赖抽象,这带来了更多优势。

“程序到接口”的意思是不要直接提供硬代码,这意味着你的代码应该在不破坏以前功能的情况下进行扩展。只是扩展,而不是编辑以前的代码。

我坚信困难的问题应该用简单的现实答案来解释。在软件设计领域,这非常重要。

看看你家里的任何,学校,教堂……任何建设

想象一下,一些得到了危险右下角(所以你必须鞠躬才能与门互动,门是打开的还是关闭的),

或者其他人只是在左上角(所以,一些矮人,残疾人,或者凯文哈特不会觉得这样的门很有趣和可用)。

所以设计是关键字,创建程序给其他人人类可以开发/使用它。

Interfaces所做的是让巨像项目[1]中的其他初级/高级开发人员变得容易,所以每个人都知道他们在做什么,而其他人几乎没有帮助,所以你可以尽可能顺利地工作(理论上)。


[1]:如何?通过公开值的形状。所以,你不需要留档,因为代码本身是不言自明的(很棒)。


这个答案并不是针对特定语言而不是概念驱动的(毕竟,人类正在通过编写代码来创建工具)。

程序到接口是GOF书中的一个术语。我不会直接说它与java接口有关,而是真正的接口。为了实现干净的层分离,你需要在系统之间创建一些分离,例如:假设你有一个你想使用的具体数据库,你永远不会“编程到数据库”,而是“编程到存储接口”。同样,你永远不会“编程到网络服务”,而是编程到“客户端接口”。这是为了让你可以轻松地交换东西。

我发现这些规则对我有帮助:

1.当我们有多种类型的对象时,我们使用java接口。如果我只有一个对象,我看不出有什么意义。如果某个想法至少有两个具体实现,那么我会使用java接口。

2.如果如上所述,您想将外部系统(存储系统)解耦到您自己的系统(本地数据库),那么也可以使用接口。

请注意,有两种方法可以考虑何时使用它们。

以前的答案专注于为了可扩展性和松耦合而对抽象进行编程。虽然这些是非常重要的一点,易读性同样重要。易读性允许其他人(以及你未来的自己)以最小的努力理解代码。这就是为什么易读性利用抽象。

根据定义,抽象比它的实现更简单。抽象省略细节以传达事物的本质或目的,但仅此而已。因为抽象更简单,与实现相比,我可以一次在脑海中容纳更多的抽象。

作为一名程序员(无论用什么语言),我脑子里一直都有一个List的概念。特别是,List允许随机访问、复制元素和维护秩序。当我看到这样的声明时:List myList = new ArrayList()我想,,这是一个List,它正在以我理解的(基本)方式使用;我不必再想了。

另一方面,我通常不会把ArrayList的具体实现细节记在脑子里,所以当我看到ArrayList myList = new ArrayList()时,我就会想,嗯哦,这个ArrayList的使用方式一定是List接口没有覆盖到的。现在我必须把这个ArrayList的所有用法都追查下来才能理解为什么,否则我将无法完全理解这段代码。当我发现这个ArrayList的所有用法都100%符合List接口时,我就更加困惑了。然后我就在想……是不是有一些依赖于ArrayList实现细节的代码被删除了?实例化它的程序员只是无能吗?这个应用程序是否在运行时以某种方式锁定在那个特定的实现中?一种我不明白的方式?

我现在对这个应用程序感到困惑和不确定,我们所谈论的只是一个简单的List。如果这是一个忽略其接口的复杂业务对象怎么办?那么我对业务领域的了解不足以理解代码的目的。

因此,即使我在private方法中严格需要List(如果它改变了,没有什么会破坏其他应用程序,而且我可以很容易地找到/替换我IDE中的每个用法),对抽象进行编程仍然具有易读性。因为抽象比实现细节更简单。你可以说,对抽象进行编程是遵守KISS原则的一种方式。

编码到接口是一种哲学,而不是特定的语言结构或设计模式——它指导您创建更好的软件系统的正确步骤顺序(例如,更具弹性、更可测试、更可扩展、更可扩展和其他好的特性)。

它实际上的意思是:

===

在跳到实现和编码(如何)之前-想想什么:

  • 你的系统应该由哪些黑盒组成,
  • 每个盒子的责任是什么?
  • 每个“客户端”(即其他盒子之一、第三方“盒子”甚至人类)应该与之通信的方式是什么(每个盒子的API)。

之后如上图所示,继续并实现这些框(如何)。

首先思考盒子是什么,它的API是什么,导致开发人员提炼盒子的责任,并为自己和未来的开发人员标记暴露细节(“API”)和隐藏细节(“实现细节”)之间的区别,这是一个非常重要的区别。

一个直接且容易注意到的收益是团队可以在不影响通用架构的情况下更改和改进实现。它还使系统更具可测试性(它与TDD方法很相配)。

==
除了我上面提到的特征之外,你还可以节省很多时间去这个方向。

微服务和DDD,如果做得好,是“接口编码”的很好的例子,但是这个概念在从单体到“无服务器”,从BE到FE,从OOP到功能等等的各种模式中获胜。

我强烈推荐这种方法用于软件工程(我基本上相信它在其他领域也完全有意义)。