什么意思是“编程接口”?
我已经看到这个提到了几次,我不清楚它的含义。 何时以及为什么要这样做?
我知道接口是做什么的,但事实上我不清楚这一点让我觉得我错过了正确使用它们。
如果你这样做,是这样吗?
IInterface classRef = new ObjectWhatever()
你可以使用任何实现IInterface
类? 你什么时候需要这样做? 我能想到的唯一的事情就是如果你有一个方法,而你不确定哪个对象会被传递给IInterface
。 我想不出多久你需要这样做…(另外,你怎么能写一个方法,接受一个实现接口的对象?这是可能的吗?)
对不起,如果我完全错过了这一点。
这里有一些关于接口和松耦合代码,控制反转等各种细节的很好的答案。 有一些相当热闹的讨论,所以我想借此机会分解一些东西,理解为什么一个接口是有用的。
当我刚开始接触界面时,我也对它们的相关性感到困惑。 我不明白你为什么需要他们。 如果我们使用的是像Java或C#这样的语言,我们已经有了inheritance,而且我将接口看作是一种较弱的inheritance和思想forms,“为什么要麻烦? 从某种意义上说,我认为接口是一种弱的inheritanceforms,但是除此之外,我最终把它们理解为一种语言结构,把它们当作一种手段来分类共同的特征或行为,潜在的许多非相关的对象类。
例如 – 说你有一个SIM游戏,并有以下类:
class HouseFly inherits Insect { void FlyAroundYourHead(){} void LandOnThings(){} } class Telemarketer inherits Person { void CallDuringDinner(){} void ContinueTalkingWhenYouSayNo(){} }
显然,这两个对象在直接inheritance方面没有共同之处。 但是,你可以说他们都很烦人。
比方说,我们的游戏需要有一些随意的事情 ,让玩家在吃晚餐的时候很烦恼。 这可能是一个HouseFly
或一个Telemarketer
HouseFly
或两者兼而有之 – 但你如何让一个单一的function? 你怎么问每一个不同types的对象以同样的方式“做他们烦人的事情”?
要认识到的关键是, Telemarketer
HouseFly
和HouseFly
都有共同的松散解释行为,尽pipe他们在build模方面没有任何相似之处。 那么,我们来创build一个可以实现的接口:
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(); } }
我们现在有两个class,每个class都可以用自己的方式烦人。 而且它们不需要从相同的基类中派生出来,并且具有共同的固有特征 – 它们只需要满足IPest
的合同即合约很简单。 你只需要BeAnnoying
。 在这方面,我们可以模拟以下内容:
class DiningRoom { DiningRoom(Person[] diningPeople, IPest[] pests) { ... } void ServeDinner() { when diningPeople are eating, foreach pest in pests pest.BeAnnoying(); } }
在这里,我们有一个餐厅,接受一些食客和一些害虫 – 注意使用界面。 这意味着,在我们的小世界里, pests
数组的成员实际上可以是Telemarketer
HouseFly
对象或HouseFly
对象。
ServeDinner
方法在晚餐时间被调用,我们在餐厅的人应该吃。 在我们的小游戏中,那就是我们的IPest
做他们的工作 – 每一个有害生物被指示为通过IPest
接口的方式恼人。 通过这种方式,我们很容易让Telemarketers
HouseFlys
和HouseFlys
以各自的方式烦人 – 我们只关心在DiningRoom
对象中有什么东西是有害生物,我们并不在乎它是什么,与其他人没有任何共同之处。
这个非常人为的伪代码示例(比我预期的要拖得更长)仅仅是为了说明在我们可能使用接口的时候,最终为我打开了一些东西。 我为这个例子的愚蠢行为提前道歉,但是希望能帮助你理解。 而且,可以肯定的是,这里发布的其他答案确实涵盖了当今devise模式和开发方法中使用接口的范围。
我以前给学生的具体例子是他们应该写
List myList = new ArrayList(); // programming to the List interface
代替
ArrayList myList = new ArrayList(); // this is bad
这些在一个简短的程序中看起来完全一样,但是如果你继续在你的程序中使用myList
100次,你可以开始看到一个区别。 第一个声明确保你只调用由List
接口定义的myList
上的方法(所以没有ArrayList
特定的方法)。 如果你已经用这种方式编程接口,以后你可以决定你真的需要
List myList = new TreeList();
你只需要在那一个地方改变你的代码。 您已经知道,您的其他代码没有做任何事情,因为您已经编程到界面,所以通过更改实现来解决任何问题。
当你谈论方法参数和返回值时,好处更加明显(我想)。 以此为例:
public ArrayList doSomething(HashMap map);
该方法声明将您ArrayList
到两个具体实现( ArrayList
和HashMap
)。 只要从其他代码调用该方法,对这些types的任何更改可能意味着您将不得不更改调用代码。 编程到接口会更好。
public List doSomething(Map map);
现在不pipe你返回什么样的List
,或者传入了什么样的Map
作为参数。 您在doSomething
方法中进行的更改不会强制您更改调用代码。
编程接口是说:“我需要这个function,我不在乎它来自哪里。”
考虑(在Java中), List
接口与ArrayList
和LinkedList
具体类。 如果我所关心的是我有一个包含多个数据项的数据结构,我应该通过迭代访问,我会select一个List
(这是99%的时间)。 如果我知道我需要从列表的任何一端进行常量插入/删除,我可能会selectLinkedList
具体实现(或者更可能使用Queue接口)。 如果我知道我需要索引随机访问,我会selectArrayList
具体类。
你应该看看控制反转:
- Martin Fowler:控制容器的倒置和dependency injection模式
- 维基百科:控制反转
在这种情况下,你不会写这个:
IInterface classRef = new ObjectWhatever();
你会写这样的东西:
IInterface classRef = container.Resolve<IInterface>();
这将进入container
对象的基于规则的设置,并为您构build实际的对象,可能是ObjectWhatever。 重要的是,你可以将这个规则replace成完全使用另一种types的对象,你的代码仍然可以工作。
如果我们把IoC从表中离开,你可以编写代码,知道它可以与一个特定的对象进行通信 ,而不是对象的types,或者它是如何执行的。
这在传递参数时会派上用场。
至于你的括号问题“另外,你怎么能写一个方法,接受一个对象,实现一个接口?这是可能的?”,在C#中,你可以简单地使用参数types的接口types,如下所示:
public void DoSomethingToAnObject(IInterface whatever) { ... }
这直接插入到“与某个特定对象进行交谈”中。 上面定义的方法知道对象期望什么,它实现了IInterface中的所有东西,但它并不关心它是哪种types的对象,只是它遵守契约,这是一个接口的真正。
例如,你可能对计算器很熟悉,在你的日子里可能会用到很多,但大多数时候他们都是不同的。 另一方面,您知道标准计算器应该如何工作,所以即使不能使用每个计算器具有的特定function,您也可以全部使用它们。
这是接口的美丽。 你可以编写一段代码,知道它将传递给它的对象,它可以期待一个特定的行为。 它并不关心一个什么样的对象,只是它支持所需的行为。
让我举一个具体的例子。
我们有一个为Windows窗体定制的翻译系统。 这个系统通过表单上的控件循环,并翻译每个文本。 系统知道如何处理基本控件,比如控件的types,文本属性和类似的基本东西,但是对于任何基本的东西来说,它都不够用。
现在,由于控件inheritance了我们无法控制的预定义类,我们可以做三件事之一:
- 在我们的翻译系统中build立支持,专门检测它正在使用哪种types的控制,并翻译正确的位(维护噩梦)
- 在基类中构build支持(不可能,因为所有控件都从不同的预定义类inheritance而来)
- 添加接口支持
所以我们做了nr。 3.我们所有的控件实现了ILocalizable,这是一个给我们一种方法的接口,通过翻译文本/规则的容器翻译“本身”的能力。 因此,表单不需要知道它find了哪种types的控件,只是它实现了特定的接口,并且知道有一个方法可以调用来定位控件。
使用接口是使代码易于testing,除去类之间不必要的耦合的关键因素。 通过创build一个定义类的操作的接口,您可以让那些希望使用该function的类能够使用它,而无需直接依赖于实现类。 如果稍后决定更改并使用不同的实现,则只需更改实例实例化的代码部分。 其余的代码不需要改变,因为它取决于接口,而不是实现类。
这在创buildunit testing中非常有用。 在被testing的类中,您可以通过构造函数或属性设置器将接口的实例注入到类中(或允许它根据需要构build接口的实例的工厂)。 该类在其方法中使用提供的(或创build的)接口。 当你去写testing的时候,你可以模拟或伪造界面,并提供一个界面来响应unit testing中configuration的数据。 你可以这样做,因为你的类只能处理接口,而不是你的具体实现。 任何实现接口的类,包括你的模拟或伪类,都可以。
编辑:下面是一篇文章的链接,其中Erich Gamma讨论他的报价,“程序到接口,而不是一个实现”。
编程接口与抽象接口完全无关,就像我们在Java或.NET中看到的那样。 这甚至不是一个面向对象的概念。
它的真正含义是不要搞乱对象或数据结构的内部。 使用抽象程序接口(或API)与您的数据进行交互。 在Java或C#中,这意味着使用公共属性和方法而不是原始字段访问。 对于C,这意味着使用函数,而不是原始指针。
编辑:与数据库,这意味着使用视图和存储过程,而不是直接表访问。
代码到接口不实现没有什么与Java或其接口构造。
这个概念在“四书通”中很突出,但很可能在此之前。 在Java存在之前,这个概念当然存在。
Java接口结构最初是为了帮助这个想法而创build的(除其他之外),人们已经越来越把注意力集中在意义的中心,而不是原意。 但是,这是我们在Java,C ++,C#等中拥有公有和私有方法和属性的原因。
它意味着只是与对象或系统的公共接口进行交互。 不要担心,甚至不能预测它是如何做到内部的。 不要担心它是如何实施的。 在面向对象的代码中,这就是为什么我们有公共方法和私有方法/属性。 我们打算使用公共方法,因为私有方法只能在课堂内部使用。 他们构成了类的实现,可以根据需要进行更改,而不必更改公共接口。 假设就function而言,每次使用相同的参数调用类时,类上的方法将执行相同的操作,并获得相同的预期结果。 它允许作者改变这个类的工作方式,实现方式,而不会影响人们如何与之交互。
而且你可以编程到接口而不是使用接口构造的实现。 你可以编程到接口而不是C ++的实现,它没有接口结构。 只要通过公共接口(合同)进行交互,而不是调用系统内部对象的方法,就可以更加健壮地集成两个大型企业系统。 在给定相同input参数的情况下,接口预期总是以相同的预期方式作出反应; 如果执行到接口而不是执行。 这个概念在很多地方都有效。
动摇Java接口对于“编程接口,而不是实现”这个概念的想法。 他们可以帮助应用这个概念,但他们不是这个概念。
这听起来像你理解接口如何工作,但不确定何时使用它们以及它们提供了什么优点。 这里有几个例子说明什么时候界面是有意义的:
// 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); }
那么我可以创buildGoogleSearchProvider,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) }
然后创buildJpegImageLoader,GifImageLoader,PngImageLoader等
大多数加载项和插件系统都可以处理接口。
另一个受欢迎的用途是用于Repository模式。 说我想加载来自不同来源的邮政编码列表
interface IZipCodeRepository { IList<ZipCode> GetZipCodes(string state); }
那么我可以创build一个XMLZipCodeRepository,SQLZipCodeRepository,CSVZipCodeRepository等等。对于我的Web应用程序,我经常在早期创buildXML存储库,以便在Sql数据库准备好之前就可以启动并运行。 数据库准备就绪后,我编写一个SQLRepository来replaceXML版本。 我的代码的其余部分保持不变,因为它运行在接口之外。
方法可以接受如下的接口:
PrintZipCodes(IZipCodeRepository zipCodeRepository, string state) { foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state)) { Console.WriteLine(zipCode.ToString()); } }
当你有一组类似的类时,它使得你的代码更具扩展性,更容易维护。 我是一名初级程序员,所以我不是专家,但是我刚刚完成了一个需要类似的项目。
我工作在与运行医疗设备的服务器交谈的客户端软件。 我们正在开发这个设备的新版本,它有一些客户必须configuration的新组件。 有两种types的新组件,它们是不同的,但它们也非常相似。 基本上,我必须创build两个configuration表单,两个列表类,两个一切。
我决定最好为每个控制types创build一个抽象基类,它将包含几乎所有的真实逻辑,然后派生types来处理这两个组件之间的差异。 但是,如果我不得不一直担心types,那么基类就无法对这些组件进行操作(当然,它们可能会有,但是会有一个“if”语句或每个方法的切换) 。
我为这些组件定义了一个简单的接口,并且所有的基类都与这个接口交谈。 现在,当我改变某些东西时,它在任何地方都可以正常工作,而且我没有代码重复。
如果你用Java编程,JDBC就是一个很好的例子。 JDBC定义了一组接口,但没有提到实现。 您的应用程序可以用这组接口来编写。 理论上,你select一些JDBC驱动程序,你的应用程序就可以工作。 如果您发现有一个更快或更好的JDBC驱动程序,或者无论出于何种原因,理论上您可以重新configuration您的属性文件,而不必对应用程序进行任何更改,您的应用程序仍然可以工作。
编程到接口是真棒,它促进松耦合。 正如@lassevk所提到的,控制反转(Inversion of Control)就是一个很好的应用。
另外,看看固体校长 。 这里是一个video系列
它经历了一个硬编码(强耦合的例子),然后看接口,最后进展到一个IoC / DI工具(NInject)
除了已经选定的答案(以及这里的各种信息post)之外,我还强烈build议您抓取Head First Design Patterns的副本。 这是一个非常简单的阅读,将直接回答你的问题,解释为什么它是重要的,并告诉你许多编程模式,你可以使用该原则(和其他)。
这里有很多解释,但要更简单些。 以一个List
为例。 可以用下面的方式实现一个列表:
- 一个内部数组
- 一个链表
- 其他的实现
通过build立一个接口,说一个List
。 你只对List的定义或者List
实际上是什么意思。
你可以使用任何types的实现在内部说一个array
实现。 但是,假设你想由于某种原因改变执行方式,说一个错误或性能。 然后,您只需将声明List<String> ls = new ArrayList<String>()
更改为List<String> ls = new LinkedList<String>()
。
没有其他的代码,你将不得不改变任何东西; 因为一切都是build立在List
定义之上的。
对于这个问题,我是一个迟到的人,但是我想在这里提到,在“四人帮”devise模式书中,“编程接口而不是实现”这一行已经有了一些很好的讨论。
它在第16页指出。 18:
编程到一个接口,而不是一个实现
不要声明variables是特定具体类的实例。 相反,只提交给抽象类定义的接口。 你会发现这是本书中devise模式的一个常见主题。
在此之上,它开始于:
单独根据抽象类定义的接口操作对象有两个好处:
- 只要客体遵守客户期望的界面,客户就不会意识到他们使用的特定types的客体。
- 客户仍然不知道实现这些对象的类。 客户只知道定义接口的抽象类。
换句话说,不要把它写成你的类,这样它就有了一个鸭子的quack()
方法,然后是一个dog的bark()
方法,因为它们对于一个类(或子类) 。 相反,使用通用的基本类giveSound()
例如giveSound()
或move()
来编写方法,以便它们可以用于鸭子,狗甚至汽车,然后用户类可以说.giveSound()
而不是考虑是否使用quack()
或bark()
或者甚至在发送正确的消息发送到对象之前确定types。
要添加到现有的文章,有时编码到接口帮助大型项目,当开发人员同时处理单独的组件。 您只需要预先定义接口并向其中写入代码,而其他开发人员则将代码写入正在实现的接口。
unit testing也是很好的,你可以将自己的类(符合接口的要求)注入到一个依赖它的类中
所以,为了解决这个问题,一个接口的好处就是我可以从任何一个特定的类中分离一个方法的调用。 而是创build一个接口的实例,其中的实现来自我select实现该接口的任何类。 因此,允许我有许多类,它们具有相似但略有不同的function,在某些情况下(涉及接口意图的情况)不关心它是哪个对象。
例如,我可以有一个移动界面。 一个方法,使某些“移动”和实现运动界面的任何对象(人,车,猫)可以被传入并被告知移动。 没有方法,每个人都知道类的types。
想象一下,你有一个名为“Zebra”的产品,可以通过插件进行扩展。 它通过在某个目录中searchDLL来查找插件。 它加载所有这些DLL并使用reflection来查找实现IZebraPlugin
任何类,然后调用该接口的方法来与插件进行通信。
这使得它完全独立于任何特定的插件类 – 它不关心类是什么。 它只关心它们是否满足接口规范。
接口是一种定义这样的可扩展性的方法。 与接口交谈的代码更松散耦合 – 实际上它根本不与任何其他特定的代码耦合。 它可以与那些从未见过原始开发人员的人编写的插件进行交互。
你可以改为使用具有虚函数的基类 – 所有的插件都是从基类派生的。 But this is much more limiting because a class can only have one base class, whereas it can implement any number of interfaces.
C++ explanation.
Think of an interface as your classes public methods.
You then could create a template that 'depends' on these public methods in order to carry out it's own function (it makes function calls defined in the classes public interface). Lets say this template is a container, like a Vector class, and the interface it depends on is a search algorithm.
Any algorithm class that defines the functions/interface Vector makes calls to will satisfy the 'contract' (as someone explained in the original reply). The algorithms don't even need to be of the same base class; the only requirement is that the functions/methods that the Vector depends on (interface) is defined in your algorithm.
The point of all of this is that you could supply any different search algorithm/class just as long as it supplied the interface that Vector depends on (bubble search, sequential search, quick search).
You might also want to design other containers (lists, queues) that would harness the same search algorithm as Vector by having them fulfill the interface/contract that your search algorithms depends on.
This saves time (OOP principle 'code reuse') as you are able to write an algorithm once instead of again and again and again specific to every new object you create without over-complicating the issue with an overgrown inheritance tree.
As for 'missing out' on how things operate; big-time (at least in C++), as this is how most of the Standard TEMPLATE Library's framework operates.
Of course when using inheritance and abstract classes the methodology of programming to an interface changes; but the principle is the same, your public functions/methods are your classes interface.
This is a huge topic and one of the the cornerstone principles of Design Patterns.
In Java these concrete classes all implement the CharSequence interface:
CharBuffer, String, StringBuffer, StringBuilder
These concrete classes do not have a common parent class other than Object, so there is nothing that relates them, other than the fact they each have something to do with arrays of characters, representing such, or manipulating such. For instance, the characters of String cannot be changed once a String object is instantiated, whereas the characters of StringBuffer or StringBuilder can be edited.
Yet each one of these classes is capable of suitably implementing the CharSequence interface methods:
char charAt(int index) int length() CharSequence subSequence(int start, int end) String toString()
In some cases Java class library classes that used to accept String have been revised to now accept the CharSequence interface. So if you have an instance of StringBuilder, instead of extracting a String object (which means instantiating a new object instance), can instead just pass the StringBuilder itself as it implements the CharSequence interface.
The Appendable interface that some classes implement has much the same kind of benefit for any situation where characters can be appended to an instance of the underlying concrete class object instance. All of these concrete classes implement the Appendable interface:
BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer
In simple terms… If I'm writing a new class Swimmer to add the functionality swim() and need to use an object of class say Dog, and this Dog class implements interface Animal which declares swim()[To better understand…you may draw a diagram as to what I am talking about]. At the top of the hierarchy(Animal) it's very abstract while at the bottom (Dog) it's very concrete. The way I think about "programming to interfaces" is that, as I write Swimmer class, I want to write my code against the interface that's as far up that hierarchy which in this case is Animal object. An interface is free from implementation details and thus makes your code loosely-coupled. The implementation details can be changed with time, however it would not affect the remaining code since all you are interacting is with the interface and not the implementation. You don't care what the implementation is like…all you know is that there will be a class that would implement the interface.
short story:Postman is asked to go home by home and receive the covers contains (letters,documents,cheques,giftcard,application,loveletter) with address written on it to deliver.
Suppose there is no cover and ask post man to go home by home and receive all the things and deliver to other person the postman can get confuse,
so better wrap it with cover(in our story it is interface) then he will do his job fine.
Now postman job is to receive and deliver the covers only..(he dont bothered what is inside in the cover).
Create type of interface
not actual type, but implement with actual type.
Create to interface means your components get Fits into the rest of code easily
I give you example.
you have AirPlane interface as below.
interface Airplane{ parkPlane(); servicePlane(); }
Suppose you have methods in your Controller class of Planes like
parkPlane(Airplane plane)
和
servicePlane(Airplane plane)
implemented in your program. It will not BREAK your code. I mean, it need not to change as long as it accepts arguments as AirPlane
.
Because it will accept any Airplane despite of actual type, flyer
, highflyr
, fighter
, etc.
Also, in a collection:
List<Airplane> plane;
// Will take all your planes.
The following example will clear your understanding.
You have a fighter plane that implements it, so
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. } }
The same thing for HighFlyer and other clasess:
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. } }
Now think your controller classes using AirPlane
several times,
Suppose your Controller class is ControlPlane like below,
public Class ControlPlane{ AirPlane plane; // so much method with AirPlane reference are used here... }
here magic comes as
you may make your new AirPlane
type instances as many as you want and you are not changing
code of ControlPlane
class.
you can add instance..
JumboJetPlane // implementing AirPlane interface. AirBus // implementing AirPlane interface.
you may remove instances.. of previously created types too.
Q: – … "You could use any class that implements interface?"
A: – Yes.Q: -… "When would you need to do that?"
A: – Each time you need a class(es) that implements interface(s).
Note: we couldn't instantiate an interface not implemented by a class – True.
- 为什么?
- because interface has only methods prototypes, not definitions (just functions names, not their logic)
AnIntf anInst = new Aclass();
// we could do this only if Aclass implements AnIntf.
// anInst will have Aclass reference.
注意:
Now we could understand what happend if Bclass and Cclass implements same 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.
What we have:
same interface prototypes (functions names in interface), and call different implementations.
Bibliography:
Prototypes – wikipedia
Interface is like contract where you want your implementation class to implement methods written in contract(Interface).Since java does not provide multiple inheritance,programming to interface is a good way to achieve purpose of multiple inheritance.If you have a class A that is already extending some other class B but you want that class A should also follow certain guidelines or implement certain contract then you can do so by programming to interface strategy.
It can be advantageous to program to interfaces, even when we are not depending on abstractions.
Programming to interfaces forces us to use a contextually appropriate subset of an object. That helps because it:
- prevents us from doing contextually inappropriate things, and
- lets us safely change the implementation in the future.
For example, consider a Person
class that implements the Friend
and the Employee
interface.
class Person implements AbstractEmployee, AbstractFriend { }
In the context of the person's birthday, we program to the Friend
interface, to prevent treating the person like an Employee
.
function party() { const friend: Friend = new Person("Kathryn"); friend.HaveFun(); }
In the context of the person's work, we program to the Employee
interface, to prevent blurring workplace boundaries.
function workplace() { const employee: Employee = new Person("Kathryn"); employee.DoWork(); }
大。 We have behaved appropriately in different contexts, and our software is working well.
Far into the future, if our business changes to work with dogs, we can change the software fairly easily. First, we create Dog
class that implements both Friend
and Employee
. Then, we safely change new Person()
to new Dog()
. Even if both functions have thousands of lines of code, that simple edit will work because we know the following are true:
- Function
party
uses only theFriend
subset ofPerson
. - Function
workplace
uses only theEmployee
subset ofPerson
. - Class
Dog
implements both theFriend
andEmployee
interfaces.
On the other hand, if either party
or workplace
were to have programmed against Person
, there would be a risk of both having Person
-specific code. Changing from Person
to Dog
would require us to comb through the code to extirpate any Person
-specific code that Dog
does not support.
The moral : programming to interfaces helps our code to behave appropriately and to be ready for change. It also prepares our code to depend on abstractions, which brings even more advantages.
Also I see a lot of good and explanatory answers here, so I want to give my point of view here, including some extra information what I noticed when using this method.
Unit testing
For the last two years, I have written a hobby project and I did not write unit tests for it. After writing about 50K lines I found out it would be really necessary to write unit tests. I did not use interfaces (or very sparingly) … and when I made my first unit test, I found out it was complicated. 为什么?
Because I had to make a lot of class instances, used for input as class variables and/or parameters. So the tests look more like integration tests (having to make a complete 'framework' of classes since all was tied together).
Fear of interfaces So I decided to use interfaces. My fear was that I had to implement all functionality everywhere (in all used classes) multiple times. In some way this is true, however, by using inheritance it can be reduced a lot.
Combination of interfaces and inheritance I found out the combination is very good to be used. I give a very simple example.
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 }
This way copying code is not necessary, while still having the benefit of using a car as interface (ICar).
Let's start out with some definitions first:
Interface n. The set of all signatures defined by an object's operations is called the interface to the object
Type n. A particular interface
A simple example of an interface as defined above would be all the PDO object methods such as query()
, commit()
, close()
etc., as a whole, not separately. These methods, ie its interface define the complete set of messages, requests that can be sent to the object.
A type as defined above is a particular interface. I will use the made-up shape interface to demonstrate: draw()
, getArea()
, getPerimeter()
etc..
If an object is of the Database type we mean that it accepts messages/requests of the database interface, query()
, commit()
etc.. Objects can be of many types. You can have a database object be of the shape type as long as it implements its interface, in which case this would be sub-typing .
Many objects can be of many different interfaces/types and implement that interface differently. This allows us to substitute objects, letting us choose which one to use. Also known as polymorphism.
The client will only be aware of the interface and not the implementation.
So in essence programming to an interface would involve making some type of abstract class such as Shape
with the interface only specified ie draw()
, getCoordinates()
, getArea()
etc.. And then have different concrete classes implement those interfaces such as a Circle class, Square class, Triangle class. Hence program to an interface not an implementation.
Program to an interface allows to change implementation of contract defined by interface seamlessly. It allows loose coupling between contract and specific implementations.
IInterface classRef = new ObjectWhatever()
You could use any class that implements IInterface? When would you need to do that?
Have a look at this SE question for good example.
为什么一个Java类的接口是首选?
does using an Interface hit performance?
if so how much?
是。 It will have slight performance overhead in sub-seconds. But if your application has requirement to change the implementation of interface dynamically, don't worry about performance impact.
how can you avoid it without having to maintain two bits of code?
Don't try to avoid multiple implementations of interface if your application need them. In absence of tight coupling of interface with one specific implementation, you may have to deploy the patch to change one implementation to other implementation.
One good use case: Implementation of Strategy pattern:
Real World Example of the Strategy Pattern
I don't retain interface
s are the most important thing in a language: it's more commonly used the class inheriting. But anyway they are important!
For example (this is Java
code, but it can simply adapted to C#
or many other languages):
interface Convertable<T> { T convert(); } public class NumerableText implements Convertable<Integer> { private String text = ""; public NumerableText() { } public NumerableText(String text) { this.text = text; } public String getText() { return this.text; } public void setText(String text) { this.text = text; } public Integer convert() { return this.text.hashCode(); } } public class NumerableTextArray implements Convertable<Integer> { private String[] textArray = ""; public NumerableTextArray() { } public NumerableTextArray(String[] textArray) { this.textArray = textArray; } public String[] getTextArray() { return this.textArray; } public void setTextArray(String[] text) { this.textArray = textArray; } public Integer convert() { Integer value = 0; for (String text : textArray) value += text.hashCode(); return value; } } public class Foo { public static void main() { Convertable<Integer> num1 = new NumerableText("hello"); Convertable<Integer> num2 = new NumerableTextArray(new String[] { "test n°1", "test n°2" }); System.out.println(String.valueOf(num1.convert())); System.out.println(String.valueOf(num2.convert())); //Here are you two numbers generated from two classes of different type, but both with the method convert(), which allows you to get that number. } }
Here is a simple example to illustrate when you program a flight booking system.
//This interface is very flexible and abstract addPassenger(Plane seat, Ticket ticket); //Boeing is implementation of Plane addPassenger(Boeing747 seat, EconomyTicket ticket); addPassenger(Cessna, BusinessClass ticket); addPassenger(J15, E87687);