在我的开发学习中,我觉得我必须学习更多关于接口的知识。

我经常读到它们,但我似乎无法理解它们。

我读过这样的例子:动物基类,IAnimal接口的东西,如“Walk”,“Run”,“GetLegs”等-但我从来没有工作过,觉得“嘿,我应该在这里使用接口!”

我错过了什么?为什么这个概念对我来说这么难理解!我只是害怕这样一个事实,我可能从来没有意识到一个具体的需要-主要是由于一些缺失的理解他们!这让我觉得我作为一名开发人员缺少了一些东西!如果有人有过这样的经历,并取得了突破,我会很感激一些关于如何理解这个概念的建议。谢谢你!


当前回答

假设你正在制作一款第一人称射击游戏。玩家有多种枪可供选择。

我们可以有一个接口Gun,它定义了函数shoot()。

我们需要不同的子类枪类,即霰弹枪狙击手等。

ShotGun implements Gun{
    public void shoot(){
       \\shotgun implementation of shoot.
    } 
}

Sniper implements Gun{
    public void shoot(){
       \\sniper implementation of shoot.
    } 
}

射击类

射手把所有的枪都装在他的盔甲里。让我们创建一个List来表示它。

List<Gun> listOfGuns = new ArrayList<Gun>();

射手在需要时使用switchGun()函数循环使用他的枪。

public void switchGun(){
    //code to cycle through the guns from the list of guns.
    currentGun = //the next gun in the list.
}

我们可以使用上面的函数设置当前的Gun,当调用fire()时,简单地调用shoot()函数。

public void fire(){
    currentGun.shoot();
}

shoot函数的行为将根据Gun接口的不同实现而有所不同。

结论

当一个类函数依赖于来自另一个类的函数时,创建一个接口,而另一个类根据实现的类的实例(对象)改变其行为。

例如,Shooter类的fire()函数期望枪械(Sniper, ShotGun)实现shoot()函数。 所以如果我们换枪开火。

shooter.switchGun();
shooter.fire();

我们已经改变了fire()函数的行为。

其他回答

假设你正在制作一款第一人称射击游戏。玩家有多种枪可供选择。

我们可以有一个接口Gun,它定义了函数shoot()。

我们需要不同的子类枪类,即霰弹枪狙击手等。

ShotGun implements Gun{
    public void shoot(){
       \\shotgun implementation of shoot.
    } 
}

Sniper implements Gun{
    public void shoot(){
       \\sniper implementation of shoot.
    } 
}

射击类

射手把所有的枪都装在他的盔甲里。让我们创建一个List来表示它。

List<Gun> listOfGuns = new ArrayList<Gun>();

射手在需要时使用switchGun()函数循环使用他的枪。

public void switchGun(){
    //code to cycle through the guns from the list of guns.
    currentGun = //the next gun in the list.
}

我们可以使用上面的函数设置当前的Gun,当调用fire()时,简单地调用shoot()函数。

public void fire(){
    currentGun.shoot();
}

shoot函数的行为将根据Gun接口的不同实现而有所不同。

结论

当一个类函数依赖于来自另一个类的函数时,创建一个接口,而另一个类根据实现的类的实例(对象)改变其行为。

例如,Shooter类的fire()函数期望枪械(Sniper, ShotGun)实现shoot()函数。 所以如果我们换枪开火。

shooter.switchGun();
shooter.fire();

我们已经改变了fire()函数的行为。

当您希望能够对多个类型使用单个变量,但所有这些类型通过接口声明实现相同的方法时,Jimmy是正确的。然后你可以在接口类型变量上调用它们的main方法。

然而,使用接口还有第二个原因。当项目架构师和实现编码员是不同的人时,或者有几个实现编码员和一个项目经理。负责人可以编写一大堆接口,并查看系统的互操作,然后让开发人员用实现类填充接口。这是确保多人编写兼容类的最佳方法,而且他们可以并行地完成。

假设你想要模拟当你试图睡觉时可能发生的烦恼。

接口前的模型

class Mosquito {
    void flyAroundYourHead(){}
}

class Neighbour{
    void startScreaming(){}
}

class LampJustOutsideYourWindow(){
    void shineJustThroughYourWindow() {}
}

正如你清楚地看到的,当你试图睡觉时,许多“事情”都可能令人讨厌。

使用没有接口的类

但是在使用这些类时,我们遇到了一个问题。他们毫无共同之处。您必须分别调用每个方法。

class TestAnnoyingThings{
    void testAnnoyingThinks(Mosquito mosquito, Neighbour neighbour, LampJustOutsideYourWindow lamp){
         if(mosquito != null){
             mosquito.flyAroundYourHead();
         }
         if(neighbour!= null){
             neighbour.startScreaming();
         }
         if(lamp!= null){
             lamp.shineJustThroughYourWindow();
         }
    }
}

带有接口的模型

为了克服这个问题,我们可以引入一个iterface

interface Annoying{
   public void annoy();

}

并在类中实现它

class Mosquito implements Annoying {
    void flyAroundYourHead(){}

    void annoy(){
        flyAroundYourHead();
    }
}

class Neighbour implements Annoying{
    void startScreaming(){}

    void annoy(){
        startScreaming();
    }
}

class LampJustOutsideYourWindow implements Annoying{
    void shineJustThroughYourWindow() {}

    void annoy(){
        shineJustThroughYourWindow();
    }
}

接口使用

这将使这些类的使用更容易

class TestAnnoyingThings{
    void testAnnoyingThinks(Annoying annoying){
        annoying.annoy();
    }
}

我偶尔也会使用接口,下面是我最新的用法(名称已经概括了):

我在WinForm上有一堆需要将数据保存到业务对象的自定义控件。一种方法是分别调用每个控件:

myBusinessObject.Save(controlA.Data);
myBusinessObject.Save(controlB.Data);
myBusinessObject.Save(controlC.Data);

这个实现的问题是,每当我添加一个控件,我必须进入我的“保存数据”方法,并添加新的控件。

我改变了我的控件来实现一个ISaveable接口,它有一个方法SaveToBusinessObject(…),所以现在我的“保存数据”方法只是通过控件迭代,如果它发现一个是ISaveable,它调用SaveToBusinessObject。所以现在当需要一个新的控件时,所有人要做的就是在该对象中实现ISaveable(并且永远不要触及其他类)。

foreach(Control c in Controls)
{
  ISaveable s = c as ISaveable;

  if( s != null )
      s.SaveToBusinessObject(myBusinessObject);
}

接口通常未被意识到的好处是本地化修改。定义之后,您很少会更改应用程序的整体流程,但通常会在细节级别上进行更改。当您将细节保存在特定对象中时,ProcessA中的更改将不会影响ProcessB中的更改。(基类也有这个好处。)

编辑:另一个好处是行动的专一性。就像在我的例子中,我所要做的就是保存数据;我不关心它是什么类型的控件,或者它是否可以做任何其他事情——我只想知道我是否可以保存控件中的数据。它使我的保存代码非常清晰——没有检查它是否为文本、数字、布尔值或任何东西,因为自定义控件处理所有这些。

扩展一下Larsenal所说的。接口是所有实现类都必须遵循的契约。因此,您可以使用一种称为契约编程的技术。这允许您的软件变得独立于实现。