编程到接口是什么意思?

我一直听到大多数编程相关网站的声明:

编程到一个接口而不是一个实现

但是我不明白这个影响
例子会有所帮助。

编辑:我收到了很多很好的答案,所以你可以补充一些代码片段,以更好地了解这个问题。 谢谢!

你可能正在寻找这样的东西:

public static void main(String... args) { // do this - declare the variable to be of type Set, which is an interface Set buddies = new HashSet(); // don't do this - you declare the variable to have a fixed type HashSet buddies2 = new HashSet(); } 

为什么认为这样做是第一种方式? 我们稍后再说,您决定需要使用不同的数据结构,比如LinkedHashSet,以利用LinkedHashSet的function。 代码必须改变如下:

 public static void main(String... args) { // do this - declare the variable to be of type Set, which is an interface Set buddies = new LinkedHashSet(); // <- change the constructor call // don't do this - you declare the variable to have a fixed type // this you have to change both the variable type and the constructor call // HashSet buddies2 = new HashSet(); // old version LinkedHashSet buddies2 = new LinkedHashSet(); } 

这似乎不是很糟糕,对吧? 但是如果你以相同的方式写getters?

 public HashSet getBuddies() { return buddies; } 

这也必须改变!

 public LinkedHashSet getBuddies() { return buddies; } 

希望你能看到,即使有这样一个小程序,你对你声明的variablestypes有深远的影响。 随着对象的来回移动,这绝对有助于程序更容易编码和维护,如果你只是依赖一个被声明为接口的variables,而不是该接口的具体实现(在这种情况下,声明它是一个设置,而不是LinkedHashSet或其他)。 它可以是这样的:

 public Set getBuddies() { return buddies; } 

还有另外一个好处,那就是(至less对我来说)差别有助于我更好地devise一个程序。 但希望我的例子给你一些想法…希望它有帮助。

有一天,一位初级程序员被老板指示编写一个应用程序来分析业务数据,并用指标,图表和所有这些东西把这些数据压缩成漂亮的报告。 老板给了他一个XML文件,上面写着“这是一些商业数据的例子”。

程序员开始编码。 几个星期后,他觉得指标和图表和东西足够满足老板,他提出了他的工作。 老板说:“那太棒了,但是它能不能显示我们这个SQL数据库的业务数据呢?”

程序员回到编码。 有从XML读取业务数据的代码遍布在他的应用程序中。 他重写了所有这些片段,用“如果”条件包装它们:

 if (dataType == "XML") { ... read a piece of XML data ... } else { .. query something from the SQL database ... } 

当提出新的软件迭代时,老板回答说:“这很好,但是它也能报告这个Web服务的业务数据吗? 记住所有那些冗长乏味的陈述,他将不得不重写,程序员变得愤怒。 “首先是XML,然后是SQL,现在是Web服务!什么是业务数据的真正来源?

老板回答说:“凡是能提供的东西”

那一刻,程序员开悟了 。

我对该陈述的初步阅读与我读过的任何答案都有很大的不同。 我同意所有的人说,使用接口types为你的方法参数等非常重要,但这不是这个声明对我意味着什么。

我的意思是它告诉你编写的代码只取决于你使用的接口(在这种情况下,我使用“接口”来表示类或接口types的暴露方法)文档。 这与编写代码的方式相反,这些代码取决于您要调用的函数的实现细节。 你应该把所有的函数调用都视为黑盒子(如果两个函数都是同一类的方法,但是理想情况下始终保持这种方法,你可以对此进行例外)。

例如:假设有一个Screen类具有Draw(image)Clear()方法。 该文件说,像“绘制方法在屏幕上绘制指定的图像”和“清除方法清除屏幕”。 如果您想要按顺序显示图像,正确的方法是反复调用Clear()然后再调用Draw() 。 这将编码到接口。 如果您正在编写实现代码,那么您可能只会调用Draw()方法,因为通过查看Draw()的实现知道它在内部调用Clear()之前执行任何绘制。 这是不好的,因为你现在依赖于你从暴露的接口看不到的实现细节。

我期待看看是否有其他人分享这个OP的问题中的这个短语的解释,或者如果我完全离开了基地… …

这是分开模块之间职责/依赖关系的一种方法。 通过定义特定的接口(API),确保接口两侧的模块不会互相“打扰”。

例如,模块1将负责显示特定用户的银行账户信息,而模块2将从任何后端获取银行账户信息。

通过定义一些types和函数以及相关的参数,例如定义银行交易的结构,以及一些方法(函数),如GetLastTransactions(AccountNumber,NbTransactionsWanted,ArrayToReturnTheseRec)和GetBalance(AccountNumer),Module1将能够获取所需的信息,而不用担心如何存储或计算这些信息或任何其他信息。 相反,Module2只是响应的方法调用通过提供的信息按照定义的接口,但不会担心这个信息的显示,打印或其他什么地方…

当一个模块被更改时,接口的实现可能会有所不同,但只要接口保持不变,使用API​​的模块可能在最坏情况下需要重新编译/重build,但是不需要修改其逻辑无论如何。

这是一个API的想法。

一个接口定义了一个对象被提交响应的方法。

当你对接口进行编码时,你可以改变底层的对象,你的代码仍然可以工作 (因为你的代码不知道WHO是否执行这项工作,或者工作是如何执行的),你可以通过这种方式获得灵活性。

当你编码到一个特定的实现时 ,如果你需要改变底层对象,你的代码很可能会中断 ,因为新对象可能不会响应相同的方法。

所以要举个明确的例子:

如果你需要保存许多对象,你可能已经决定使用Vector 。

如果你需要访问Vector的第一个对象,你可以这样写:

  Vector items = new Vector(); // fill it Object first = items.firstElement(); 

到现在为止还挺好。

后来你决定,因为“有些”的原因,你需要改变实现(假设Vector由于过度同步而产生瓶颈)

你意识到你需要使用一个ArrayList instad。

那么,你的代码将打破…

 ArrayList items = new ArrayList(); // fill it Object first = items.firstElement(); // compile time error. 

你不能。 这条线和所有那些使用firstElement()方法的线会中断。

如果你需要特定的行为,并且你肯定需要这个方法,那么可能是好的(虽然你不能改变实现) 但是如果你需要的是简单地检索第一个元素(也就是说,没有什么特别的Vector另外它有firstElement()方法),那么使用接口而不是实现会给你更改的灵活性。

  List items = new Vector(); // fill it Object first = items.get( 0 ); // 

在这个表单中,你不是编码Vector的get方法 ,而是编写List的get方法 。

不pipe底层对象如何执行该方法,只要它响应“获取集合的第0个元素”的合约,

这样您可以稍后将其更改为任何其他实现:

  List items = new ArrayList(); // Or LinkedList or any other who implements List // fill it Object first = items.get( 0 ); // Doesn't break 

这个示例可能看起来很幼稚,但却是OO技术基础的基础(即使是那些非Python,Ruby,Smalltalk,Objective-C等静态types的语言)

一个更复杂的例子是JDBC的工作方式。 您可以更改驱动程序,但大部分通话都将以相同的方式进行。 例如,您可以使用标准的Oracle数据库驱动程序,也可以使用Weblogic或Webpshere提供的更复杂的驱动程序。 当然,这不是神奇的,你以前还必须testing你的产品,但至less你没有这样的东西:

  statement.executeOracle9iSomething(); 

VS

 statement.executeOracle11gSomething(); 

Java Swing发生了类似的情况。

补充阅读:

devise模式的devise原则

有效的Java项目:通过其接口引用对象

(购买这本书是你可以在生活中做的最好的事情之一 – 如果当然,阅读 – )

这个说法的核心就是关于依赖关系。 如果我编写我的Foo类到一个实现( Bar而不是IBar ),那么Foo现在依赖于Bar 。 但是,如果我编写我的类Foo到接口( IBar而不是Bar ),那么实现可能会有所不同,并且Foo不再依赖于特定的实现。 这种方法提供了一个灵活的,松散耦合的代码库,更容易重用,重构和unit testing。

一个接口就像是你和接口的合约,你的代码将执行他们所要求的接口。 此外,你想要以这样一种方式对事物进行编码,使得你的解决scheme可以多次解决问题。 认为代码重用。 当你编码到一个实现时,你纯粹是想要解决一个问题的实例。 所以在这种影响下,你的解决scheme将不那么通用和更专注。 这将使写作一个通用的解决scheme,遵守一个更具挑战性的接口。

取一个红色的2×4乐高积木,并将其连接到一个蓝色的2×4乐高积木,这样一个坐在另一个上面。 现在取下蓝色块,并用黄色的2×4乐高块replace。 请注意,即使附加块的“实现”不同,红色块也不必更改。

现在去获得一些不共享乐高“界面”的其他types的块。 尝试将其附加到红色2×4乐高。 要做到这一点,你需要改变乐高或其他块,也许通过切割一些塑料或添加新的塑料或胶水。 请注意,通过改变“实施”,你不得不改变它或客户端。

在不改变客户端或服务器的情况下,可以让实现变化 – 这就是编程接口的意义。

看,我不知道这是Java的,我的代码是基于C#,但我相信它提供了重点。

每辆车都有门。

但是并不是所有的门都是一样的,就像英国的出租车门是倒退的一样。 一个普遍的事实是他们“开放”和“closures”。

 interface IDoor { void Open(); void Close(); } class BackwardDoor : IDoor { public void Open() { // code to make the door open the "wrong way". } public void Close() { // code to make the door close properly. } } class RegularDoor : IDoor { public void Open() { // code to make the door open the "proper way" } public void Close() { // code to make the door close properly. } } class RedUkTaxiDoor : BackwardDoor { public Color Color { get { return Color.Red; } } } 

如果你是一个汽车门维修人员,你不在乎门的外观,或者如果它打开的方式或其他方式。 你唯一的要求就是门像IDoor那样的门。

 class DoorRepairer { public void Repair(IDoor door) { door.Open(); // Do stuff inside the car. door.Close(); } } 

修理者可以处理RedUkTaxiDoor,RegularDoor和BackwardDoor。 和其他任何types的门,如卡车门,豪华轿车门。

 DoorRepairer repairer = new DoorRepairer(); repairer.Repair( new RegularDoor() ); repairer.Repair( new BackwardDoor() ); repairer.Repair( new RedUkTaxiDoor() ); 

应用这个列表,你有LinkedList,Stack,Queue,普通列表,如果你想要你自己的MyList。 他们都实现了IList接口,这需要他们实现添加和删除。 所以,如果你的class级添加或删除任何给定列表中的项目…

 class ListAdder { public void PopulateWithSomething(IList list) { list.Add("one"); list.Add("two"); } } Stack stack = new Stack(); Queue queue = new Queue(); ListAdder la = new ListAdder() la.PopulateWithSomething(stack); la.PopulateWithSomething(queue); 

Allen Holub在2003年为JavaWorld撰写了一篇名为Why extend is evil的文章 。 他可以从他的头衔中得到关于“程序到界面”的说法,那就是你应该愉快地实现接口,但是很less使用extends关键字来inheritance子类。 他指出,除其他外,所谓的脆弱的基层阶级问题。 维基百科:

一个面向对象的编程系统的基本架构问题,其中基类(超类)被认为是“脆弱的”,因为对派生类inheritance的看起来对基类的安全修改可能导致派生类发生故障。 程序员不能简单地通过单独检查基类的方法来确定基类更改是否安全。

除了其他的答案,我补充说:

你编程到一个接口,因为它更容易处理。 该接口封装了底层类的行为。 这样,class级就是一个黑匣子。 你的整个现实生活正在编程到一个界面。 当你使用电视,汽车,立体声系统时,你是在其界面而不是在其实施细节上采取行动,并假设如果实施改变(例如柴油发动机或天然气),界面保持不变。 编程到接口允许您在不中断的细节更改,优化或修复时保持行为。 这也简化了logging,学习和使用的任务。

另外,编程到一个接口允许你在写代码之前描述你的代码的行为。 你希望一个class级做一些事情。 甚至在你编写实际的代码之前,你可以testing一下。 当你的界面干干净净,并且你喜欢与之交互时,你可以编写实际的代码。

“编程接口”可以更灵活。

例如,我们正在写一个打印机提供打印服务。 目前有2类( Dog )需要打印。 所以我们写如下代码

 class Printer { public void PrintCat(Cat cat) { ... } public void PrintDog(Dog dog) { ... } ... } 

如果有一个新class级, Bird还需要这个打印服务吗? 我们必须改变Printer类来添加一个新的方法PrintBird()。 在实际情况下,当我们开发Printer类时,我们可能不知道谁会使用它。 那么如何写Printer ? 编程到一个接口可以帮忙,看下面的代码

 class Printer { public void Print(Printable p) { Bitmap bitmap = p.GetBitmap(); // print bitmap ... } } 

使用这款新型打印机,只要实现Interface Printable接口Printable ,就可以打印所有内容。 这里的方法GetBitmap()只是一个例子。 关键是暴露一个接口不是一个实现。

希望这是有帮助的。

从本质上讲,接口是互操作的一般概念稍微更具体的表示 – 他们提供了规范,你可能会关心“插入”特定function的所有各种选项应该做类似的,以便使用它们的代码将不会取决于一个特定的选项。

例如,许多DB库充当接口,它们可以使用许多不同的实际DB(MSSQL,MySQL,PostgreSQL,SQLite等),而无需使用DB库的代码根本不必改变。

总的来说,它允许您创build更灵活的代码 – 为您的客户提供更多的使用方式选项,还可能让您在多个地方更容易地重复使用代码,而无需编写新的专用代码。

通过编程接口,你更可能应用低耦合/高凝聚原理。 通过对接口进行编程,您可以轻松切换该接口(特定类)的实现。

这意味着你的variables,属性,参数和返回types应该有一个接口types,而不是一个具体的实现。

这意味着您使用IEnumerable<T> Foo(IList mylist)而不是ArrayList Foo(ArrayList myList)

仅在构造对象时使用实现:

 IList list = new ArrayList(); 

如果你已经这样做了,你可以稍后改变对象types,也许你想在后面使用LinkedList而不是ArrayList,这是没有问题的,因为在其他地方你把它称为“IList”

基于接口的编程提供了在运行时没有与特定对象的强耦合。 由于Java中的对象variables是多态的,所以超类的对象引用可以引用其任何子类的对象。 使用超types声明的对象可以分配属于超types的任何特定实现的对象。

注意,作为一个接口可以使用一个抽象类。

在这里输入图像描述

基于实现编程:

 Motorcycle motorcycle = new Motorcycle(); motorcycle.driveMoto(); 

基于接口编程:

 Vehicle vehicle; vehicle = new Motorcycle(); // Note that DI -framework can do it for you vehicle.drive(); 

这基本上就是你在这里创build一个方法/接口的地方: create( 'apple' )方法create(param)来自抽象类/接口fruit ,稍后由具体类实现。 这与子类不同。 您正在创build一个类必须履行的合同。 这也减less了耦合,使每个具体类在不同的地方实现它更灵活。

客户端代码仍然不知道所使用的特定types的对象,并且不知道实现这些对象的类。 客户端代码只知道接口create(param) ,并使用它来创build水果对象。 就好像在说:“我不在乎你是怎么得到的,只是想让你给我。”

类比这是一组开关button。 这是on()off()的接口。 您可以在几个设备上使用这些button,电视,收音机,灯光。 他们都以不同的方式处理他们,但我们不关心这一点,我们只关心打开或closures它。