我已经看过几次提到这个,我不清楚这是什么意思。你何时以及为何会这样做?
我知道界面有什么用,但事实上我不清楚这一点让我觉得我错过了正确使用它们。
如果你这样做的话就是这样:
IInterface classRef = new ObjectWhatever()
您可以使用任何实现IInterface
的类吗?你什么时候需要这样做?我唯一能想到的是,如果你有一个方法,你不确定将传递什么对象,除了它实现IInterface
。我想不出你需要多久做一次。
另外,你怎么能写一个接受实现接口的对象的方法?这可能吗?
答案 0 :(得分:1529)
这里有一些很好的答案,这些问题涉及各种关于接口和松散耦合代码,控制反转等等的详细信息。有一些相当令人兴奋的讨论,所以我想借此机会分解一下,以了解界面为何有用。
当我第一次接触界面时,我也对它们的相关性感到困惑。我不明白你为什么需要它们。如果我们使用像Java或C#这样的语言,我们已经有了继承,我将接口视为弱形式的继承和思想,“为什么要这么麻烦?”从某种意义上说,我是对的,你可以把接口看作是一种弱的继承形式,但除此之外,我最终将它们理解为一种语言结构,将它们视为一种分类表现出来的共同特征或行为的手段。可能有许多不相关的对象类。
例如 - 假设你有一个SIM游戏并且有以下类:
class HouseFly inherits Insect {
void FlyAroundYourHead(){}
void LandOnThings(){}
}
class Telemarketer inherits Person {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
}
显然,这两个对象在直接继承方面没有任何共同之处。但是,你可以说他们都很讨厌。
假设我们的游戏需要有一些随机的 thing ,这会让玩家在吃晚餐时烦恼。这可能是HouseFly
或Telemarketer
或两者兼而有之 - 但您如何同时使用单一功能?你如何要求每种不同类型的对象以同样的方式“做他们讨厌的事情”?
要意识到的关键是,Telemarketer
和HouseFly
共享一个常见的松散解释行为,即使它们在建模方面没有任何相似之处。所以,让我们创建一个可以实现的接口:
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 pests
pest.BeAnnoying();
}
}
在这里,我们有一个餐厅,可以容纳一些食客和一些害虫 - 请注意界面的使用。这意味着在我们的小世界中,pests
数组的成员实际上可能是Telemarketer
对象或HouseFly
对象。
当晚餐送达时我们会调用ServeDinner
方法,我们在餐厅的人应该吃。在我们的小游戏中,当我们的害虫开始工作时 - 通过IPest
界面指示每种害虫都很烦人。通过这种方式,我们可以轻松地让Telemarketers
和HouseFlys
以自己的方式烦恼 - 我们只关心我们在DiningRoom
对象中有什么东西是害虫,我们并不在乎它是什么,它们与其他人没有任何共同之处。
这个非常人为的伪代码示例(拖延的时间比我预期的要长很多)只是为了说明在我们使用接口的时候最终为我提供的那种东西。我为这个例子的愚蠢提前道歉,但希望它有助于你的理解。而且,可以肯定的是,您在此处收到的其他答案确实涵盖了当今设计模式和开发方法中使用接口的全部内容。
答案 1 :(得分:256)
我以前给学生的具体例子是他们应该写
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
方法中所做的更改不会强制您更改调用代码。
答案 2 :(得分:69)
对界面进行编程时说:“我需要这个功能而我不关心它来自哪里。”
考虑(在Java中)List
接口与ArrayList
和LinkedList
具体类。如果我关心的是我有一个包含多个数据项的数据结构,我应该通过迭代访问,我会选择List
(这是99%的时间)。如果我知道我需要从列表的任何一端插入/删除常量时间,我可能会选择LinkedList
具体实现(或者更可能使用Queue接口)。如果我知道我需要通过索引进行随机访问,我会选择ArrayList
具体类。
答案 3 :(得分:36)
除了删除类之间不必要的耦合之外,使用接口是使代码易于测试的关键因素。通过创建定义类的操作的接口,您允许希望使用该功能的类能够使用它而无需直接依赖于您的实现类。如果您稍后决定更改并使用其他实现,则只需更改实例实例化的代码部分。其余的代码不需要改变,因为它取决于接口,而不是实现类。
这在创建单元测试时非常有用。在被测试的类中,您依赖于接口并通过构造函数或属性settor将接口实例注入类(或允许它根据需要构建接口实例的工厂)。该类在其方法中使用提供的(或创建的)接口。当您编写测试时,您可以模拟或伪造接口,并提供一个响应单元测试中配置的数据的接口。您可以这样做,因为您所测试的类仅处理接口,而不是您的具体实现。任何实现该接口的类(包括您的模拟或假类)都可以。
编辑:以下是一篇文章的链接,其中Erich Gamma讨论了他的引用,“程序到界面,而不是实现。”
答案 4 :(得分:35)
你应该研究控制反转:
在这种情况下,你不会写这个:
IInterface classRef = new ObjectWhatever();
你会写这样的东西:
IInterface classRef = container.Resolve<IInterface>();
这将进入container
对象中基于规则的设置,并为您构造实际对象,可以是ObjectWhatever。重要的是你可以用完全使用其他类型对象的东西替换这个规则,你的代码仍然可以工作。
如果我们将IoC从表中删除,您可以编写代码,知道它可以与某个特定的对象进行通信,但不能与哪种类型的对象或它是如何进行对话。
传递参数时,这会派上用场。
至于你的括号问题“另外,你怎么能写一个接受实现接口的对象的方法?这可能吗?”,在C#中你只需要使用参数类型的接口类型,如下所示:
public void DoSomethingToAnObject(IInterface whatever) { ... }
这直接插入“与特定事物的对象交谈”。上面定义的方法知道对象的期望,它实现了IInterface中的所有内容,但它并不关心它是哪种类型的对象,只是它遵守契约,这就是接口。
例如,您可能熟悉计算器,并且可能在您的日子里使用了很多,但大多数时候它们都是不同的。另一方面,你知道标准计算器应该如何工作,所以即使你不能使用每个计算器都没有的特定功能,你也可以全部使用它们。
这是界面之美。你可以编写一段代码,知道它会传递给它的对象,它可以期待某些行为。它并不关心它是什么类型的对象,只是它支持所需的行为。
让我举一个具体的例子。
我们为Windows窗体提供了一个定制的翻译系统。该系统循环访问表单上的控件并在每个表单中翻译文本。系统知道如何处理基本控件,比如控件类型 - 具有文本属性,以及类似的基本内容,但对于任何基本控件,它都不足。
现在,由于控件继承自我们无法控制的预定义类,我们可以做以下三件事之一:
所以我们做了nr。 3.我们所有的控件都实现了ILocalizable,这是一个为我们提供一种方法的接口,即将“自身”转换为翻译文本/规则容器的能力。因此,表单不需要知道它找到了哪种控件,只需知道它实现了特定的接口,并且知道有一种方法可以调用本地化控件。
答案 5 :(得分:31)
对接口的编程与我们在Java或.NET中看到的抽象接口完全无关。它甚至不是OOP的概念。
它的真正含义是不要乱搞对象或数据结构的内部。使用抽象程序接口或API与您的数据进行交互。在Java或C#中,这意味着使用公共属性和方法而不是原始字段访问。对于C,这意味着使用函数而不是原始指针。
编辑:对于数据库,它意味着使用视图和存储过程而不是直接表访问。
答案 6 :(得分:23)
接口的代码并非实现与Java及其接口构造无关。
这个概念在“图案/四人帮”中引人注目,但很可能在此之前就已经存在。 这个概念在Java出现之前肯定存在。
创建Java接口构造是为了帮助这个想法(除其他外),人们已经过于专注于构造作为意义的中心而不是原始意图。但是,这就是我们在Java,C ++,C#等中使用公共和私有方法和属性的原因。
这意味着只需与对象或系统的公共界面进行交互。不要担心甚至预测它如何在内部完成它的作用。不要担心它是如何实现的。在面向对象的代码中,这就是我们拥有公共方法/私有方法/属性的原因。我们打算使用公共方法,因为私有方法只在内部使用,在类中。它们构成了类的实现,可以根据需要进行更改,而无需更改公共接口。假设关于功能,每次使用相同的参数调用类时,类上的方法将执行相同的操作,并具有相同的预期结果。它允许作者改变类的工作方式及其实现,而不会破坏人们与之交互的方式。
您可以编程到接口,而不是实现而不使用接口构造。您可以编程到接口而不是C ++中的实现,它没有Interface构造。只要通过公共接口(契约)进行交互而不是在系统内部的对象上调用方法,就可以更加健壮地集成两个大型企业系统。在给定相同输入参数的情况下,接口应始终以相同的预期方式作出反应;如果实现到接口而不是实现。这个概念在许多地方都有效。
认为Java接口有什么与“接口编程”而不是“实现”的概念有关。他们可以帮助应用这个概念,但他们不这个概念。
答案 7 :(得分:12)
听起来您理解界面如何工作但不确定何时使用它们以及它们提供的优势。以下是一个界面何时有意义的例子:
// 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);
}
然后我可以创建一个XMLZipCodeRepository,SQLZipCodeRepository,CSVZipCodeRepository等。对于我的Web应用程序,我经常在早期创建XML存储库,这样我就可以在Sql数据库准备好之前启动并运行。数据库准备好后,我编写一个SQLRepository来替换XML版本。我的其余代码保持不变,因为它从接口运行。
方法可以接受以下接口:
PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
{
Console.WriteLine(zipCode.ToString());
}
}
答案 8 :(得分:10)
当您拥有类似的类集时,它使您的代码更具可扩展性并且更易于维护。我是一名初级程序员,所以我不是专家,但我刚刚完成了一个需要类似的项目。
我在客户端软件上工作,该软件与运行医疗设备的服务器通信。我们正在开发此设备的新版本,其中包含一些客户必须有时配置的新组件。有两种类型的新组件,它们是不同的,但它们也非常相似。基本上,我必须创建两个配置表单,两个列表类,两个一切。
我决定最好为每个控件类型创建一个抽象基类,它几乎包含所有真实逻辑,然后派生类型来处理两个组件之间的差异。但是,如果我不得不一直担心类型,那么基类就无法对这些组件执行操作(好吧,它们可能有,但是每个方法都会有“if”语句或开关)
我为这些组件定义了一个简单的接口,所有基类都与此接口通信。现在,当我改变某些东西时,它几乎“无处不在”,我没有代码重复。
答案 9 :(得分:8)
如果使用Java编程,JDBC就是一个很好的例子。 JDBC定义了一组接口,但没有说明实现。您的应用程序可以根据这组接口编写。从理论上讲,您选择了一些JDBC驱动程序,您的应用程序就可以运行。如果您发现有更快或更好或更便宜的JDBC驱动程序或出于任何原因,您可以再次理论上重新配置您的属性文件,而无需对您的应用程序进行任何更改,您的应用程序仍然可以正常工作。
答案 10 :(得分:8)
编程到接口非常棒,它促进了松耦合。正如@lassevk所提到的,反转控制是一个很好的用途。
此外,请查看SOLID主体。 here is a video series
通过硬编码(强耦合示例)然后查看接口,最后进入IoC / DI工具(NInject)
答案 11 :(得分:7)
我是这个问题的后来者,但我想在这里提到线路&#34;程序到接口,而不是实现&#34;在GoF(Gang of Four)设计模式书中有一些很好的讨论。
它说,p。 18:
编程到界面,而不是实现
不要将变量声明为特定具体类的实例。相反,只提交由抽象类定义的接口。你会发现这是本书设计模式的一个共同主题。
以上,它始于:
仅仅根据抽象类定义的接口来操作对象有两个好处:
- 客户端仍然不知道他们使用的特定类型的对象,只要这些对象符合客户期望的接口即可。
- 客户端仍然不知道实现这些对象的类。客户端只知道定义接口的抽象类。
醇>
换句话说,不要把它写成你的类,以便它有一个quack()
方法用于鸭子,然后用bark()
方法用于狗,因为它们太具体了类(或子类)的特定实现。相反,使用通常足以在基类中使用的名称来编写方法,例如giveSound()
或move()
,以便它们可以用于鸭子,狗,甚至汽车,然后您的类的客户端可以只说.giveSound()
而不是考虑是否使用quack()
或bark()
,甚至在发出正确的消息发送到对象之前确定类型。
答案 12 :(得分:6)
除了已经选择的答案(以及此处的各种信息性帖子),我强烈建议您抓取Head First Design Patterns的副本。这是一个非常简单的阅读,将直接回答你的问题,解释为什么它是重要的,并向你展示许多编程模式,你可以使用这些模式来利用这个原则(和其他)。
答案 13 :(得分:6)
有很多解释,但要使它更简单。
以List
为例。可以使用as:
通过构建界面,请说出List
。您只需对List的定义或List
在现实中的含义进行编码。
您可以在内部使用任何类型的实现来说明array
实施。但是假设你希望改变实现,出于某种原因说出错误或性能。然后,您只需将声明List<String> ls = new ArrayList<String>()
更改为List<String> ls = new LinkedList<String>()
。
否则代码中的其他地方,您是否必须更改其他内容;因为其他一切都建立在List
的定义之上。
答案 14 :(得分:4)
要添加到现有帖子,当开发人员同时处理单独的组件时,有时编码到接口有助于大型项目。您只需要预先定义接口并向其编写代码,而其他开发人员将代码编写到您正在实现的接口上。
答案 15 :(得分:4)
它也适用于单元测试,您可以将自己的类(满足接口要求)注入依赖于它的类
答案 16 :(得分:3)
即使我们不依赖于抽象,也可以对接口进行编程。
对接口进行编程强制我们使用对象的上下文相应子集。这有助于因为它:
例如,考虑实现Person
和Friend
接口的Employee
类。
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
和Friend
的{{1}}类。然后,我们安全地将Employee
更改为new Person()
。即使两个函数都有数千行代码,这个简单的编辑也会起作用,因为我们知道以下内容是正确的:
new Dog()
仅使用party
的{{1}}子集。 Friend
仅使用Person
的{{1}}子集。 workplace
实现Employee
和Person
接口。 另一方面,如果Dog
或Friend
已针对Employee
进行了编程,则可能存在party
特定代码的风险。从workplace
更改为Person
需要我们梳理代码以删除Person
不支持的Person
特定代码。
道德:对接口的编程有助于我们的代码正常运行并为变革做好准备。它还准备我们的代码依赖于抽象,这带来了更多的优势。
答案 17 :(得分:3)
C ++解释。
将接口视为类公共方法。
然后,您可以创建一个“依赖”这些公共方法的模板,以便执行它自己的函数(它使类公共接口中定义的函数调用)。让我们说这个模板是一个容器,就像Vector类一样,它依赖的接口是一个搜索算法。
任何定义函数/接口Vector的算法类都会使调用满足'契约'(正如原始回复中所解释的那样)。算法甚至不需要是相同的基类;唯一的要求是你的算法中定义了Vector依赖的函数/方法(接口)。
所有这一切的要点是,只要提供了Vector所依赖的界面(气泡搜索,顺序搜索,快速搜索),就可以提供任何不同的搜索算法/类。
您可能还希望设计其他容器(列表,队列),这些容器将使用与搜索算法所依赖的接口/契约相同的搜索算法。
这节省了时间(OOP原则'代码重用'),因为您可以编写一次算法,而不是一次又一次地特定于您创建的每个新对象,而不会过度复杂化问题与过度生长的继承树。< / p>
至于“错过”事情的运作方式;大时间(至少在C ++中),因为这是标准模板库的大部分框架运行的方式。
当然,当使用继承和抽象类时,对接口进行编程的方法会发生变化;但原理是一样的,你的公共函数/方法是你的类接口。
这是一个巨大的主题,也是设计模式的基石原则之一。
答案 18 :(得分:3)
所以,为了做到这一点,接口的优点是我可以将方法的调用与任何特定的类分开。而是创建一个接口实例,其中的实现是从我选择的实现该接口的类中给出的。因此,允许我拥有许多类,这些类具有相似但略有不同的功能,在某些情况下(与界面意图相关的情况)并不关心它是哪个对象。
例如,我可以有一个移动界面。使某事物“移动”的方法和实现运动界面的任何物体(人物,汽车,猫)都可以被传递并被告知移动。没有这个方法,每个人都知道它的类型。
答案 19 :(得分:3)
想象一下,你有一个名为'Zebra'的产品可以通过插件进行扩展。它通过在某个目录中搜索DLL来找到插件。它加载所有这些DLL并使用反射来查找实现IZebraPlugin
的任何类,然后调用该接口的方法与插件进行通信。
这使得它完全独立于任何特定的插件类 - 它不关心类是什么。它只关心它们是否符合接口规范。
接口是一种定义可扩展性点的方法。与接口通信的代码更松散耦合 - 实际上它根本不与任何其他特定代码耦合。它可以与多年后由未见过原始开发人员的人一起编写的插件进行互操作。
您可以使用具有虚函数的基类 - 所有插件都将从基类派生。但这更具限制性,因为一个类只能有一个基类,而它可以实现任意数量的接口。
答案 20 :(得分:2)
简单来说...... 如果我正在编写一个新的类Swimmer来添加函数swim()并且需要使用类的对象说Dog,而这个Dog类实现了接口Animal来声明swim()[为了更好地理解...你可以画一个关于我在说什么的图表]。在层次结构的顶部(动物)它是非常抽象的,而在底部(狗)它是非常具体的。我认为“编程到接口”的方式是,当我编写Swimmer类时,我想编写我的代码来反对该层次结构的接口,在这种情况下是Animal对象。接口没有实现细节,因此使代码松散耦合。实现细节可以随着时间的推移而改变,但是它不会影响剩余的代码,因为你所有的交互都是使用接口而不是实现。你不关心实现是什么样的......你所知道的是会有一个实现接口的类。
答案 21 :(得分:2)
短篇小说:邮递员被要求在家中回家,并收到包含(信件,文件,支票,礼品卡,应用程序,情书)的封面,上面写着送达的地址。
假设没有封面,并要求邮递员回家,收到所有的东西并交给其他人邮递员可能会混淆,
好多了 用封面包裹它(在我们的故事中它是界面)然后他将完成他的工作。
现在邮递员的工作就是接收和交付封面..(他不会打扰封面内的内容)。
创建interface
类型不是实际类型,但使用实际类型实现。
创建界面意味着您的组件轻松适应其余代码
我举个例子。
您的AirPlane界面如下所示。
interface Airplane{
parkPlane();
servicePlane();
}
假设您在Controller类的Planes中有方法,如
parkPlane(Airplane plane)
和
servicePlane(Airplane plane)
在您的计划中实施。它不会 BREAK 您的代码。
我的意思是,只要它接受AirPlane
的参数,就不需要改变。
因为它会接受任何实际类型的飞机,flyer
,highflyr
,fighter
等等。
另外,在一个集合中:
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和其他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.
}
}
现在多次使用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.
您也可以删除以前创建的类型的实例..
答案 22 :(得分:2)
在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,OutputStreamWriter, PipedWriter,PrintStream,PrintWriter, StringBuffer,StringBuilder, StringWriter,Writer
答案 23 :(得分:2)
接口就像一个契约,你希望你的实现类实现在契约(接口)中编写的方法。由于Java不提供多重继承,“编程到接口”是实现多重继承的好方法。
如果您的A类已经扩展了其他B类,但您希望A类也遵循某些指导原则或实施某个合同,那么您可以通过“编程到接口”策略来实现。< / p>
答案 24 :(得分:1)
问: - ......“你可以使用任何实现接口的类吗?”
问: - ......“你什么时候需要这样做?” 答: - 每次你需要一个实现接口的类。
答:是的。
注意: 我们无法实例化类未实现的接口 - True。
AnIntf anInst = new Aclass();
// 只有在 Aclass实现AnIntf时才能执行此操作。
// 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.
我们拥有什么:
相同的接口原型(接口中的函数名称),并调用不同的实现。
参考书目:
Prototypes - wikipedia
答案 25 :(得分:1)
程序接口允许无缝地更改接口定义的合同的实现。它允许合同和特定实现之间的松散耦合。
IInterface classRef = new ObjectWhatever()
您可以使用任何实现IInterface的类吗?你什么时候需要这样做?
看看这个SE问题就是一个很好的例子。
Why should the interface for a Java class be preferred?
是否使用了界面效果?
如果是这样多少?
是。它会在亚秒内产生轻微的性能开销。但是,如果您的应用程序需要动态更改接口的实现,请不要担心性能影响。
如何在不必维护两位代码的情况下避免使用它?
如果您的应用程序需要,请不要试图避免多个接口实现。如果没有接口与一个特定实现的紧密耦合,您可能必须部署补丁以将一个实现更改为其他实现。
一个好的用例:战略模式的实施:
答案 26 :(得分:0)
此外,我在这里看到了很多好的和解释性的答案,所以我想在这里提出我的观点,包括我在使用这种方法时注意到的一些额外信息。
单元测试
在过去的两年里,我写了一个爱好项目,我没有为它编写单元测试。在写了大约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)。
答案 27 :(得分:0)
让我们首先介绍一些定义:
界面 n。 对象操作定义的所有签名集称为对象的接口
输入 n。 特定界面
如上所定义的接口的简单示例将是所有PDO对象方法,例如query()
,commit()
,close()
等。整个,不是单独的。这些方法,即其接口定义了完整的消息集,可以发送给对象的请求。
上面定义的类型是一个特定的界面。我将使用虚构的形状界面来演示:draw()
,getArea()
,getPerimeter()
等。
如果对象属于数据库类型,则表示它接受数据库接口的消息/请求,query()
,commit()
等。对象可以是多种类型。只要数据库对象实现其接口,就可以使其具有形状类型,在这种情况下,这将是子类型。
许多对象可以有许多不同的接口/类型,并以不同的方式实现该接口。这允许我们替换对象,让我们选择使用哪一个。也称为多态。
客户端只会知道接口而不是实现。
因此,实际上对接口的编程将涉及使用仅Shape
,draw()
,getCoordinates()
等指定接口的某些类型的抽象类,例如getArea()
。然后有不同的具体类实现那些接口,如Circle类,Square类,Triangle类。 因此编程接口而不是实现。
答案 28 :(得分:0)
我不保留interface
是语言中最重要的东西:它更常用于继承类。但无论如何它们很重要!
例如(这是Java
代码,但它可以简单地适应C#
或许多其他语言):
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.
}
}
答案 29 :(得分:0)
“程序到接口”表示未正确地提供硬代码,这意味着应在不破坏先前功能的情况下扩展代码。只是扩展,而不编辑先前的代码。
答案 30 :(得分:0)
我坚信,应该用简单的现实答案来解释难题。在软件设计领域,它非常重要。
看房子,学校,教堂中的任何门 ...任何建筑物。
想象一下门的危险在右下(因此,您必须鞠躬才能与打开或关闭的门互动),
或者其他人刚好位于左上(因此,一些矮人,残疾人或凯文·哈特(Kevin Hart)不会发现这类门很有趣且实用)。
因此 design 是关键字,可以为其他人创建程序,人类可以开发/使用它。
Interfaces
的作用是使庞大的项目中的其他初级/高级开发人员变得容易 [1] ,因此每个人都知道他们在做什么,而很少得到他人的帮助,因此您可以(理论上)尽可能地平滑。
[1] 怎么样?通过暴露价值的形状。因此,您实际上不需要文档,因为代码本身是不言自明的(很棒)。
这个答案不是针对特定语言的,而是针对概念驱动的(毕竟,人类通过编写代码来创建工具)。
答案 31 :(得分:0)
接口程序是GOF书中的术语。我不会直接说它与Java接口有关,而是与实际接口有关。为了实现干净的层分离,您需要在系统之间创建一些分离,例如:假设您有一个要使用的具体数据库,则永远不要“编程到数据库”,而要“编程到存储接口”。同样,您将永远不会“编程到Web服务”,而只会编程到“客户端接口”。这样一来,您可以轻松地将内容交换出去。
我发现这些规则对我有帮助:
1 。当我们有多种类型的对象时,我们使用Java接口。如果我只有一个对象,我看不到重点。如果至少有一些想法的具体实现,那么我将使用一个Java接口。
2 。如果如上所述,您想将外部系统(存储系统)的解耦引入自己的系统(本地DB),则还可以使用接口。
注意何时有两种方法考虑使用它们。希望这可以帮助。
答案 32 :(得分:0)
为了可扩展性和松散耦合,以前的答案侧重于对抽象进行编程。虽然这些都是非常重要的点, 可读性同样重要。可读性允许其他人(以及你未来的自己)以最少的努力理解代码。这就是可读性利用抽象的原因。
根据定义,抽象比其实现更简单。抽象省略细节以传达事物的本质或目的,但仅此而已。 因为抽象更简单,所以与实现相比,我可以一次在脑海中容纳更多的抽象。
作为一名程序员(使用任何语言),我四处走动,脑海中始终都有一个 List
的大致概念。特别是,List
允许随机访问、重复元素并保持顺序。当我看到这样的声明时:List myList = new ArrayList()
我想,cool,这是一个以我理解的(基本)方式使用的 List
;我不用再考虑了。
另一方面,我并没有在脑海中浮现ArrayList
的具体实现细节。所以当我看到时,ArrayList myList = new ArrayList()
。我认为,呃-哦,这个 ArrayList
必须以 List
接口未涵盖的方式使用。现在我必须追踪这个 ArrayList
的所有用法才能理解为什么,否则我将无法完全理解这段代码。当我发现这个 ArrayList
do 100% 的用法都符合 List
接口时,我就更加困惑了。然后我想知道......是否有一些依赖于 ArrayList
实现细节的代码被删除了?实例化它的程序员只是无能吗?此应用程序是否在运行时以某种方式锁定在该特定实现中?我不明白的方式?
我现在对这个应用程序感到困惑和不确定,我们所谈论的只是一个简单的 List
。如果这是一个忽略其接口的复杂业务对象怎么办?那么我对业务领域的了解不足以理解代码的用途。
因此,即使我在 List
方法中严格需要一个 private
(如果更改不会破坏其他应用程序,并且我可以轻松找到/替换我的 IDE 中的每个用法),它仍然有好处编程抽象的可读性。因为抽象比实现细节更简单。您可以说对抽象进行编程是遵守 KISS 原则的一种方式。
答案 33 :(得分:-2)
这是一个简单的例子,用于说明您编制航班预订系统的时间。
//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);