什么时候应该使用接口?
我知道一个接口没有一个方法定义的主体。 但是什么时候我应该使用接口? 如果我为某人提供一组没有身体的接口,为什么他们觉得需要编写函数体? 他们会用抽象的方法写出自己的抽象类更好吗?
编辑:
如果你是一个团队的一员,我想接口的使用更多。 假设团队A写了一些代码,他们想看看是否调用方法。 与名称getRecords(),是否完成。 这将帮助团队B编写提供给他们的界面主体,团队B必须保持方法名称相似,以便团队A的代码运行。
只是一个想法。 我可能错了。 我认为接口对单个开发人员没有用处。
编辑:
感谢所有的答案。 用你们所有人的回答,我认为接口有更多的用途,当你正在做类似的API?
在诸如Java和C#接口的语言中,为类提供了一种多态的方式。 也就是说一个类可以满足一个以上的合同 – 它可以performance为多种不同的types,一种类别可以替代另一种types。 在其他语言中,这也可以通过多inheritance来提供,但是这种方法存在各种缺点。 但是,拥有一个类的行为不止一种types不是使用接口的最常见的动机。
通过对接口而不是类进行编程,您也可以将程序从特定的实现中解耦。 这使得用一个类实现replace另一个类变得容易得多。 这在编写unit testing时特别有用,你可能希望用一个轻量级的模拟对象来交换一些重量级的类实现。 如果你的程序只需要一个接口types,并且重量级对象和模拟对象都实现了这个接口,那么它们很容易被replace。
另外,考虑一个简单的Java例子,我说有一个程序,在屏幕上显示的数据页面。 最初我希望它从数据库或XML文件中获取数据。 如果我编写我的程序,以便它使用接口,我可以像这样定义一个接口:
public interface PageDatasource { public List<Page> getPages(); }
像这样使用它:
PageDatasource datasource = // insert concrete PageDatasource implementation here List<Pages> pages = datasource.getPages(); display(pages);
然后,我可以编写单独的数据库和遵守这个接口的XML实现:
public class DatabasePageDatasource implements PageDatasource { public List<Page> getPages() { // Database specific code } } public class XmlPageDatasource implements PageDatasource { public List<Page> getPages() { // XML specific code } }
因为我使用了一个接口,所以我现在可以互换使用实现 – 数据库或XML – 而不必更改我的程序中要求页面数据的部分。 XML和数据库实现可能做完全不同的事情,但是我的程序关心的是提供页面数据的对象实现了PageDatasource
接口。
界面真实生活比喻:
假设你想从图书馆发行一本书。 你会做什么:
1)去图书馆
2)find你感兴趣的书
3)select书
4)去图书pipe理员处要求他们出书。
现在这里图书pipe理员是一个界面,这意味着你对谁是地狱是图书pipe理员不感兴趣,你只对那个坐在图书pipe理员桌子上的人感兴趣(是同意作为图书pipe理员的合同的人,这意味着该人同意实施图书馆员的一切行为)
所以说 :图书馆员的行为是:
1)发行书
2)重新发行书
3)退货书。
被指定为图书馆员的人(即同意适应上述三种行为的人)必须以自己的方式实施这些行为。
假设PersonA想要扮演图书馆员的angular色,那么他需要适应上述3个行为。 我们作为客户不关心他如何执行图书馆员的行为,我们只关心那个人是图书pipe理员,他遵守图书pipe理员的任务。
因此,你要做什么,是由接口引用[去图书pipe理员书桌(这将导致你作为图书pipe理员的人)],并说在未来一个人离开图书pipe理员职位,然后作为客户端,它不会影响你,如果你已经走近了图书pipe理员的桌子 ,而不是具体的人担任图书pipe理员 。所以代码接口,而不是具体的是有益的。
class PersonA implements Librarian { public void IssueBook(){...} public void ReIssueBook(){...} public void ReturnBook(){...} //Other person related tasks... }
即使作为一个开发人员的接口可以是一个非常有用的工具。 让我试着用一个例子来说明。
假设你正在开发一个pipe理图书馆目录的系统,图书馆将借出书籍和DVD。 您决定创buildBook和Dvd类来模拟借出的项目,但是现在您要实现多态,以便处理项目而不是书籍或DVD。 问题是Item是一个抽象类还是一个接口?
在这个例子中,你可能想要使用一个抽象类,因为你可以通过父类提供Book和Dvd的共同function,例如检出或返回一个项目。
现在让我们假设你想为你的图书馆目录实现一个持久化机制。 你已经决定要在数据库中存储一些数据,在XML文件中存储一些数据,在逗号分隔的文件中存储一些数据。 所以现在的问题是如何以一种多态的方式去做这件事情,让你可以处理一般的持久性API呢?
在这种情况下,您应该定义一个接口,可以由您的每个类实现,提供数据库,XML和逗号分隔的持久性,因为每个持久性机制都提供了类似的function,即存储和检索数据,但每个都会以不同的方式实现。 这将允许您轻松更改您正在使用的持久性机制,而无需对使用持久性机制的代码进行大量更改。
乍一看,抽象类和接口看起来像是一个明智之举。 为什么只提供一个接口,当你也可以提供一些基础实现? 经过调查,你会发现还有更多。
也就是说,使用接口有很多原因。 你可以在这里find一个不错的博客文章。
这就是说,考虑一个事实,你可以创build一个接口(一个“契约”,说你的类绝对支持某些调用/方法签名调用),但是你只能提供一个抽象类。 另外考虑一下这个事实,即你可以创build一个也实现一个或多个接口的抽象类,并从中inheritance。 这不是一个边缘情况。 这实际上是在API的高扩展性中经常进行的。
看看我指向你的博客文章,你应该彻底理解什么时候使用每一个,为什么你会使用它们。 我也强烈推荐一本好书,比如微软出版社的“CLR via C#”。 你会学到很多!
接口存在的原因是由于OOP的2个原则概念,即“身份”和“function”
类具有function和身份。 通过inheritance,实例化的对象可以有很多function和多个身份。
接口是没有function的身份。 该function将由实例化的类提供。
第三种forms,“mixin”是没有身份的function。 像Ruby这样的编程语言提供了第三种inheritanceforms。
接口的使用方式因编程语言和环境而异,但请记住,接口用于定义在对象上执行的身份。
接口有助于澄清不同function单元之间的区别。 一个单位依靠另一个单位来做某件事,而不是某件事。 只要其他人能做到界面上的规定(想想契约 ),那么它可以是幕后的任何事情。
例如,我有一个入口处理器,从一个地方读取条目,并将它们写入另一个地方。 它不关心什么/在哪里,或什么/在哪里。 所有它关心的是从一些types的阅读器(使用IReader接口) 入口 ,并将它们交给某种types的写入器(使用IWriter接口)。
在我的第一个实现中, IReader实现者从SQL数据库中获取东西, IWriter实现者通过Web服务客户端发布它。 但是,我们最终还是会创build其他实现者来访问其他存储库(FTP站点,本地networking驱动器上的文件目录等)。
所有这一切中间的处理器并不在乎,也不会改变。 它只是通过这些标准接口进行交谈。
理论上你可以使用一个基类(最好是抽象类)而不是一个接口,但是开始将模块更紧密地锁在一起,这使得你的系统难以维护。 即使你不是一个程序员团队,失去联系确实会让你的生活更轻松。 把你自己看作是一个不同的程序员,每次你在系统的不同部分工作。 每次你重新访问一个给定的部分,你必须重新学习如何去维护它。 如果你的系统的每个部分都与其他部分紧密结合在一起,那么你必须对整个系统有一个持续的,深入的了解,而不仅仅是你正在工作的一个部分。
还有一个类实现多个接口的概念,就像多重inheritance一样。 在这种情况下,一个class级可以执行多个工作(可以说这不是很明智,但至less是可能的)。 如果你select使用基类而不是上面的接口,那么这是不可能的。
为了增加以前的答案,接口可以帮助你在unit testing期间允许你在代码中注入一个基于接口的模拟对象,允许你模拟特定的场景,也可以把你的unit testing隔离到一个特定的组件,而不依赖于外部组件。
例如,假设您有一个业务逻辑类,它使用数据逻辑类从数据源检索数据,然后对其进行处理。 通过为inheritance的数据逻辑类创build一个接口,可以基于该接口创build该类的模拟/伪造实例,并将其注入到您正在进行unit testing的业务逻辑类中。 模拟实例可以被定义,以期望某些方法/属性调用,在某些点抛出exception,返回有效的输出等等。这意味着,你的unit testing可以运行得更快/潜在更可靠,因为它们不依赖于底层数据源可用/实际上不必连接到它。 你正在将unit testing隔离到一个特定的代码单元。
一个接口比一个抽象类更好,因为你可以实现多个接口,并且只能从一个抽象类inheritance。
所以你可以这样做:
class MyRow extends AbstractRow implements ISearchable, ISortable { }
此外,searchStackOverflow的其他类似的问题,如需要在C#
接口和抽象类有不同的用途。 首先,抽象类只能用于inheritance – 它不能直接实例化。 您可以将实现代码放在抽象类中,但只能从扩展它的类或通过扩展它的类中调用该代码。
例:
public abstract class A { public void myFunc() { //do some work } } public class B : A { private void someOtherFunc() { base.myFunc(); } } public class Z { public void exampleFunc() { //call public base class function exposed through extending class: B myB = new B(); myB.myFunc(); //explicitly cast B to A: ((A) myB).myFunc(); //'A' cannot be created directly, do implicit cast down to base class: A myA = new B(); myA.myFunc(); } }
接口的目的是提供一个契约 – 这意味着实现它的类必须为接口中声明的属性或函数提供实现,并且必须使用完全相同的函数签名。 当我编写一些调用实现接口的类的代码时,这提供了保证,并且这些类是从哪个派生而来的,甚至是写入什么语言,或者从哪里得到的。 另一个很酷的function是,我可以为不同的接口有相同function签名的几个不同的实现。 检查这个例子,它使用了上一个例子中的一些相同的类:
public class Z { public void exampleFunc() { C myC = new C(); //these two calls both call the same function: myC.myFunc(); ((IExampleInterface1)myC).myFunc(); D myD = new D(); //hmmm... try and work out what happens here, which myFunc() gets called? //(clue: check the base class) myD.myFunc(); //call different myFunc() in same class with identical signatures: ((IExampleInterface1)myD).myFunc(); ((IExampleInterface2)myD).myFunc(); } } interface IExampleInterface1 { void myFunc(); } interface IExampleInterface2 { void myFunc(); } public class C : IExampleInterface1 { public void myFunc() { //do stuff } } public class D : A, IExampleInterface1, IExampleInterface2 { void IExampleInterface1.myFunc() { //this is called when D is cast as IExampleInterface1 } void IExampleInterface2.myFunc() { //this is called when D is cast as IExampleInterface2 } }
这是一个devise问题(我谈论Java世界)。
该接口允许您定义软件的组件(类)的行为和结构,而不会在运行时限制您的限制。
相反,抽象类为您提供了一种方法的默认行为:有用的,如果你确信这个代码可能不会在应用程序的lyfe时间发生变化。
例:
您有一个在某些情况下发送电子邮件的商业系统的Web应用程序(注册一个新用户)。
如果您确定行为不会改变oftern,您可以使用抽象类SenderCommunication与方法布尔sendWithSuccefull(…)。
如果您不确定行为不会经常更改,则可以使用接口InterfaceSenderCommunication和方法boolean sendWithSuccefull(…)。
当然,“经常改变 – 不经常”的判断取决于两个因素:
- 我必须花费多less时间才能将旧代码与新规格同步?
- 客户付多less钱? ;)
让我们假设我有FoodEstablishment类,现在我想做简单的CRUD操作,那么我该怎么做呢? 我定义了一个服务接口,但为什么?
public interface IFoodEstablishmentService { Task<int> AddAsync(FoodEstablishment oFoodEstablishment); FoodEstablishment SelectByFoodEstablishmentId(long id); Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment); Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment); }
然后,我将执行该特定服务的合同或界面
public class FoodEstablishmentService : IFoodEstablishmentService { public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment) { // Insert Operation return result; } public FoodEstablishment SelectByFoodEstablishmentId(long id) { // Select Logic return oFoodEstablishment; } public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment) { // Update Logic return result; } public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment) { // Delete Logic return result; } }
所以在我的主要程序或者我希望使用服务的地方,我会做的
IFoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentService(); FoodEstablishment oFoodEstablishment = // Input might be from views; oFoodEstablishmentService.AddAsync(oFoodEstablishment);
所以到现在为止,我们可以直接完成,似乎是一个额外的步骤
FoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentService(); FoodEstablishment oFoodEstablishment = // Input might be from views; oFoodEstablishmentService.AddAsync(oFoodEstablishment);
但是等待如果我可能需要通过队列而不是直接将插入逻辑传递给服务器,等待插入操作完成然后返回结果,而是传递队列然后队列工作人员处理这些操作,可能不是最好的主意队列插入,但肯定是好的接口的例子:-)。 所以现在我所做的就是创build另一个实施同样合同IFoodEstablishment的FoodEstablishment。
public class FoodEstablishmentQueueService : IFoodEstablishmentService { public async Task<int> AddAsync(FoodEstablishment oFoodEstablishment) { // Insert Queue Operation return result; } public FoodEstablishment SelectByFoodEstablishmentId(long id) { // Select Queue Logic return oFoodEstablishment; } public async Task<int> UpdateAsync(FoodEstablishment oFoodEstablishment) { // Update Queue Logic return result; } public async Task<int> DeleteAsync(FoodEstablishment oFoodEstablishment) { // Delete Queue Logic return result; } }
所以现在如果我想使用队列版本,我只会这样做
IFoodEstablishmentService oFoodEstablishmentService = new FoodEstablishmentQueueService(); FoodEstablishment oFoodEstablishment = // Input might be from views; oFoodEstablishmentService.AddAsync(oFoodEstablishment);
我们可以用旧的方法做类,但是使用类来定义类实例化,而这个类实际上是一个对扩展有严格要求的类,现在FoodEstablishmentQueueService也可以做其他的事情,创build另一个方法,直到合约有效,所以interface是一致的对一个做正常版本的人和其他做队列版本的人或者做某个caching版本的人进行映像,除非预先指定了有关工作合同和人员最终不会交叉检查所有内容,否则签名上可能会有问题。
同样,考虑使用IEnumerable等预定义types的其他简单示例。 假设我传递了FoodEstablishment集合的列表并返回自定义sorting列表
public FoodEstablishment[] SortFoodEstablishment(FoodEstablishment[] list) { foreach(var oFoodEstablishment in list) { // some logic } return sortedList; }
所以我们会这样使用它
FoodEstablishment[] list = new FoodEstablishment[]{ //some values } var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);
但是,如果我们发送列表,而不是数组
List<FoodEstablishment> list = //some values; var listSorted = oFoodEstablishmentService.SortFoodEstablishment(list);
我们会得到错误,因为它使用类的严格实现,所以我们使用IEnumerable <>,它是由List实现的,基本上从List到Interface
public IEnumerable<FoodEstablishment> SortFoodEstablishment(IEnumerable<FoodEstablishment> list) { foreach(var oFoodEstablishment in list) { // some logic } return sortedList; }
所以平时实施情况很大程度取决于情况
使用接口的一个原因是一个类将实现多个接口。 抽象类不能这样做。
一个例子是处理鼠标移动的类,按键将实现(ficticious)IMouseMove和IKeyPress接口。
另外,使用接口可以简化unit testing。 为了testing依赖于接口的类,并且只要你使用某种dependency injection,就可以创build实现依赖接口的存根类,也可以使用模拟引擎。
Java lang不支持多inheritance,因为接口是用来实现目标的。
对于一个抽象的类只有一个抽象的方法, 而在接口的情况下,所有的方法都是抽象的。
抽象类v / s接口始终是开发人员讨论的一个问题。 我会加5美分。 当你想要扩展一个comman库,并且你想为抽象方法提供一个默认的实现时,使用抽象类。
当你想把所有的抽象方法正确地实现到实现接口的类时使用接口,并且不能提供默认的方法。
将对HashSet或TreeSet的引用存储在Settypes的variables中被认为是很好的风格。
Set<String> names = new HashSet<String>();
这样,如果决定使用TreeSet
,则必须只更改一行。
另外,对集合进行操作的方法应该指定typesSet的参数:
public static void print(Set<String> s)
然后该方法可以用于所有设置的实现。
理论上,我们应该对链接列表做出相同的build议,即将LinkedList引用保存到Listtypes的variables中。 但是,在Java库中,List接口对于ArrayList
和LinkedList
类都是通用的。 特别是,它已经获得并设置了随机访问的方法,即使这些方法对于链接列表来说效率非常低。
如果不知道随机访问是否有效,则无法编写高效的代码。 这是标准库中的一个严重的devise错误,我不能推荐使用List接口。
(看看这个错误是多么令人尴尬,看一下Collections类的binarySearch方法的源代码,该方法需要一个List参数,但二进制search对链表没有意义,代码笨拙地尝试发现列表是否是链表,然后切换到线性search!)
Set
界面和Map
界面,devise得很好,你应该使用它们。
接口保持定义和实现分开。所以如果你想供应商具体实现,那么这是做到这一点的最好方法。
现实生活中的例子是JDBC如果你看到API Statemnet,PreparedStatemnet Everything是接口,但是实现是厂商特定的,比如Oracle ojdbc jar有一些其他的实现,mysql有一些其他的实现。
如果你想改变数据库,你只需要在连接类名中input驱动nad。 这是使用界面的优势
界面的使用:
- 定义合同
- 链接不相关的类有一个function (例如,实现Serializable接口的类除了实现该接口之外,它们之间可能有也可能没有任何关系
- 提供可互换的实现,例如
Strategy_pattern
如果您正在寻找java作为编程语言,那么自Java-8发布以来, interface
具有更多的function,增加了default
和static
方法。
有关更多详细信息,请参阅此问题:
为什么要使用接口,多inheritance与接口,接口的好处?
属于一个类别的对象可以使用一个接口。当对象之间存在任何IS-A关系时。
如文本,图像,MP3,FLV,MOV属于FileType类别
如果我们的应用程序允许用户上传4种文件types,则它们全部属于一个名为FILE TYPE的类别
- 文本
- 图片
- audio
- video
在内部代码中,我们将有对上述每种types的对象。
假设我们需要发送一个文件
带出接口
与接口
所以我们可以假定所有的文件types(文本,audio等)都是FileFormattypes的,所以我们形成一个IS-A关系。
这有助于返回单一数据types,而不是从函数返回特定types,我们可以发送通用types,即FileFormat。