“编程到界面”是什么意思?

问题描述 投票:765回答:32

我已经看过几次提到这个,我不清楚这是什么意思。你何时以及为何会这样做?

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

如果你这样做是这样的:

IInterface classRef = new ObjectWhatever()

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

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

language-agnostic oop interface
32个回答
1547
投票

这里有一些很好的答案可以得到各种关于接口和松散耦合代码,控制反转等等的详细信息。有一些相当令人兴奋的讨论,所以我想借此机会分解一下,以了解界面为何有用。

当我第一次开始接触界面时,我也对它们的相关性感到困惑。我不明白你为什么需要它们。如果我们使用像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 pests
        pest.BeAnnoying();
    }
}

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

当晚餐供应时我们会调用ServeDinner方法,我们在餐厅的人应该吃。在我们的小游戏中,当我们的害虫完成他们的工作时 - 每种害虫都被指示通过IPest界面令人讨厌。通过这种方式,我们可以很容易地让TelemarketersHouseFlys以他们自己的方式烦恼 - 我们只关心我们在DiningRoom对象中有什么东西是害虫,我们并不在乎它是什么,他们可以与其他人没有任何共同之处。

这个非常人为的伪代码示例(拖延的时间比我预期的要长很多)仅仅是为了说明在我们使用接口的时候最终为我提供的那种东西。我为这个例子的愚蠢提前道歉,但希望它有助于你的理解。而且,可以肯定的是,您在此处收到的其他答案确实涵盖了当今设计模式和开发方法中使用接口的全部内容。


8
投票

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


8
投票

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

另外,请研究SOLID原则。 qazxsw poi

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


7
投票

我是这个问题的后来者,但我想在这里提到“程序到界面,而不是实现”这一行在GoF(Gang of Four)设计模式书中有一些很好的讨论。

它说,在第。 18:

编程到接口,而不是实现

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

以上,它始于:

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

  1. 只要对象遵循客户期望的接口,客户端就不会意识到它们使用的特定类型的对象。
  2. 客户端仍然不知道实现这些对象的类。客户端只知道定义接口的抽象类。

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


6
投票

除了已经选择的答案(以及这里的各种信息性帖子),我强烈建议抓一份bark()。这是一个非常简单的阅读,将直接回答您的问题,解释它为什么重要,并向您展示许多编程模式,您可以使用这些模式来使用该原则(和其他)。


6
投票

那里有很多解释,但要使它更简单。以Head First Design Patterns为例。可以使用as实现列表:

  1. 内部数组
  2. 链表
  3. 其他实施

通过建立一个界面,比如说List。你只编写List的定义或List在现实中的含义。

您可以在内部使用任何类型的实现来说明List实现。但是假设你希望改变实现,出于某种原因说出错误或性能。然后你只需要将声明array更改为List<String> ls = new ArrayList<String>()

在代码中没有其他地方,你是否需要改变其他任何东西;因为其他一切都建立在List<String> ls = new LinkedList<String>()的定义上。


4
投票

要添加到现有帖子,当开发人员同时处理单独的组件时,有时编码到接口有助于大型项目。您只需要预先定义接口并向其编写代码,而其他开发人员将代码编写到您正在实现的接口上。


4
投票

它也适用于单元测试,您可以将自己的类(满足接口要求)注入依赖于它的类中


3
投票

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

例如,我可以有一个移动界面。使某事物“移动”的方法和实现运动界面的任何物体(人物,汽车,猫)都可以被传递并被告知移动。没有这个方法,每个人都知道它的类型。


3
投票

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

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

接口是一种定义可扩展性点的方法。与接口通信的代码更松散耦合 - 实际上它根本不与任何其他特定代码耦合。它可以与多年后由未见过原始开发人员的人一起编写的插件进行互操作。

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


3
投票

C ++解释。

将接口视为类公共方法。

然后,您可以创建一个“依赖”这些公共方法的模板,以便执行它自己的函数(它使类公共接口中定义的函数调用)。让我们说这个模板是一个容器,就像Vector类一样,它依赖的接口是一个搜索算法。

任何定义函数/接口Vector的算法类都会使调用满足'契约'(如原始回复中所解释的那样)。算法甚至不需要是相同的基类;唯一的要求是在您的算法中定义了Vector依赖的函数/方法(接口)。

所有这一切的关键在于,只要提供了Vector所依赖的接口(气泡搜索,顺序搜索,快速搜索),您就可以提供任何不同的搜索算法/类。

您可能还希望设计其他容器(列表,队列),这些容器将使用与Vector相同的搜索算法,使其满足您的搜索算法所依赖的接口/合约。

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

至于“错过”事物的运作方式;大时间(至少在C ++中),因为这是标准TEMPLATE Library框架的大部分运作方式。

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

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


258
投票

我以前给学生的具体例子是他们应该写作

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方法中所做的更改不会强制您更改调用代码。


3
投票

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

对接口进行编程迫使我们使用对象的上下文相应子集。这有助于因为它:

  1. 阻止我们做上下文不适当的事情,并且
  2. 让我们安全地改变将来的实施。

例如,考虑一个实现IZebraPluginPerson接口的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(); } 类,实现DogFriend。然后,我们安全地将Employee改为new Person()。即使两个函数都有数千行代码,这个简单的编辑也会起作用,因为我们知道以下内容是正确的:

  1. 函数new Dog()仅使用partyFriend子集。
  2. 函数Person仅使用workplaceEmployee子集。
  3. Person实现了DogFriend接口。

另一方面,如果Employeeparty要对workplace进行编程,那么两者都存在具有Person特定代码的风险。从Person更改为Person将要求我们梳理代码以消除Dog不支持的任何特定于Person的代码。

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


2
投票

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

CharBuffer,String,StringBuffer,StringBuilder

这些具体类没有除Object之外的公共父类,因此没有任何内容与它们相关,除了它们各自与字符数组有关,表示这样或者操纵它们的事实。例如,一旦String对象被实例化,就不能更改String的字符,而可以编辑StringBuffer或StringBuilder的字符。

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

Dog

在某些情况下,用于接受String的Java类库已被修改为现在接受CharSequence接口。因此,如果你有一个StringBuilder实例,而不是提取一个String对象(这意味着实例化一个新的对象实例),那么只需在实现CharSequence接口时传递StringBuilder本身。

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

BufferedWriter,CharArrayWriter,CharBuffer,FileWriter,FilterWriter,LogStream,OutputStreamWriter,PipedWriter,PrintStream,PrintWriter,StringBuffer,StringBuilder,StringWriter,Writer


2
投票

简单来说......如果我正在编写一个新类Swimmer来添加函数swim()并且需要使用类的对象说Dog,而这个Dog类实现了接口Animal,它声明了swim()[为了更好地理解。 ..你可以画一张关于我在说什么的图表]。在层次结构的顶部(动物)它是非常抽象的,而在底部(狗)它是非常具体的。我认为“编程到接口”的方式是,当我编写Swimmer类时,我想编写我的代码来反对该层次结构的接口,在这种情况下是Animal对象。接口没有实现细节,因此使代码松散耦合。实现细节可以随着时间的推移而改变,但是它不会影响剩余的代码,因为你所有的交互都是使用接口而不是实现。你不关心实现是什么......所有你知道的是会有一个实现接口的类。


2
投票

简短的故事:邮递员被要求在家里回家,收到的封面包括(信件,文件,支票,礼品卡,应用程序,情书),上面写着送达的地址。

假设没有封面,并要求邮递员回家,收到所有的东西,并交给其他人,邮递员可能会混淆,

所以最好用封面包裹它(在我们的故事中它是界面)然后他将完成他的工作。

现在邮递员的工作就是接收和交付封面......(他不会打扰封面内的内容)。

创建char charAt(int index) int length() CharSequence subSequence(int start, int end) String toString() 类型而不是实际类型,但使用实际类型实现。

创建界面意味着您的组件可以轻松地安装到其余代码中

我举个例子。

你有AirPlane界面如下。

interface

假设您在Controller类的Planes中有方法

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

parkPlane(Airplane plane)

在你的程序中实现。它不会破坏您的代码。我的意思是,只要它接受servicePlane(Airplane plane) 作为参数,它就不需要改变。

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

另外,在一个集合中:

fighter //将带走你所有的飞机。

以下示例将清楚您的理解。


你有一架实现它的战斗机,所以

List<Airplane> plane;

HighFlyer和其他类同样的事情:

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.
    }
}

现在想几次使用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. } } 你的控制器类,

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

AirPlane

魔术来了

您可以根据需要制作新的public Class ControlPlane{ AirPlane plane; // so much method with AirPlane reference are used here... } 类型实例,但不会改变

AirPlane类的代码。

你可以添加实例..

ControlPlane

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


2
投票

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

如果您的A类已经扩展了其他B类,但是您希望A类也遵循某些指导原则或实现某个合同,那么您可以通过“编程到接口”策略来实现。


1
投票

问: - ......“你可以使用任何实现接口的类吗?” 答:是的。

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

注意:我们无法实例化一个未由类实现的接口 - True。

  • 为什么?
  • 因为接口只有方法原型,而不是定义(只是函数名,而不是它们的逻辑)

AnIntf​​ anInst = new A Class(); //我们只有在Class实现AnIntf​​时才能这样做。 // anInst将具有Aclass引用。


注意: 现在我们可以理解如果B类和C类实现相同的Diff会发生什么。

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

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

参考书目: 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.


1
投票

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

Prototypes - wikipedia

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

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

IInterface classRef = new ObjectWhatever()

使用界面命中性能?

如果是这样多少?

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

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

如果您的应用程序需要,请不要试图避免多个接口实现。如果没有接口与一个特定实现的紧密耦合,您可能必须部署补丁以将一个实现更改为其他实现。

一个很好的用例:战略模式的实施:

Why should the interface for a Java class be preferred?


0
投票

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

单元测试

在过去的两年里,我写了一个爱好项目,我没有为它编写单元测试。在写了大约50K行之后,我发现编写单元测试是非常必要的。我没有使用接口(或非常谨慎)......当我进行第一次单元测试时,我发现它很复杂。为什么?

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

害怕接口所以我决定使用接口。我担心的是我必须多次在所有使用的类中实现所有功能。在某种程度上,这是正确的,但是,通过使用继承,它可以减少很多。

接口和继承的结合我发现组合非常好用。我举一个非常简单的例子。

Real World Example of the Strategy Pattern

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


0
投票

我们先从一些定义开始:

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

输入n。一个特定的界面

如上定义的界面的简单示例将是所有PDO对象方法,例如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 } query()commit()等,作为整体,而不是单独的。这些方法,即其接口定义了完整的消息集,可以发送给对象的请求。

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

如果一个对象属于数据库类型,我们的意思是它接受数据库接口的消息/请求,getPerimeter()query()等。对象可以有多种类型。只要数据库对象实现其接口,就可以使其具有形状类型,在这种情况下,这将是子类型。

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

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

所以本质上编程到一个接口将涉及制作一些类型的抽象类,如commit()与接口只指定,即Shapedraw()getCoordinates()等。然后有不同的具体类实现这些接口,如Circle类,Square类,三角类。因此,程序到接口而不是实现。


0
投票

我不保留getArea()s是一种语言中最重要的东西:它更常用于继承类。但无论如何它们很重要! 例如(这是interface代码,但它可以简单地适应Java或许多其他语言):

C#

69
投票

对界面进行编程时说:“我需要这个功能,而我并不关心它来自哪里。”

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


0
投票

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


36
投票

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

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

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

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


35
投票

你应该研究控制反转:

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

IInterface classRef = new ObjectWhatever();

你会写这样的东西:

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

这将进入container对象中基于规则的设置,并为您构造实际对象,可以是ObjectWhatever。重要的是,你可以用完全使用其他类型对象的东西替换这个规则,你的代码仍然可以工作。

如果我们将IoC从表中删除,您可以编写代码,该代码知道它可以与执行某些特定操作的对象进行通信,但不能使用哪种类型的对象或如何进行操作。

传递参数时这会派上用场。

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

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

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

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

这是界面之美。你可以编写一段代码,知道它会传递给它的对象,它可以期待某些行为。它并不关心它是什么类型的对象,只是它支持所需的行为。

让我举一个具体的例子。

我们为Windows窗体提供了一个定制的翻译系统。该系统循环访问表单上的控件并在每个表单中翻译文本。系统知道如何处理基本控件,比如控件类型有文本属性,以及类似的基本内容,但对于任何基本控件,它都不足。

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

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

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


32
投票

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

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

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


24
投票

接口的代码并非实现与Java及其接口构造无关。

这个概念在“图案/四人帮”中引人注目,但很可能在此之前就已经存在。在Java出现之前,这个概念确实存在。

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

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

您可以在不使用Interface构造的情况下编程到接口,而不是实现。您可以编程到接口而不是C ++中的实现,它没有Interface构造。只要通过公共接口(契约)进行交互而不是在系统内部的对象上调用方法,就可以更加健壮地集成两个大型企业系统。在给定相同输入参数的情况下,接口应始终以相同的预期方式作出反应;如果实现到接口而不是实现。这个概念在许多地方都有效。

摇晃Java接口与“接口程序而不是实现”概念有任何关系的想法。他们可以帮助应用这个概念,但它们不是概念。


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

10
投票

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

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

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

我为这些组件定义了一个简单的接口,所有基类都与这个接口通信。现在,当我改变某些东西时,它几乎“无处不在”,我没有代码重复。

© www.soinside.com 2019 - 2024. All rights reserved.