接口的功能主要是在不知道如何构建类的情况下使用函数吗?

时间:2009-06-27 02:37:02

标签: oop interface

据我所知,接口是契约,我把它解释为契约词,即必须具有接口中指定的内容(ex打开,关闭,读取,写入接口处理文件)。

但是我很难掌握的是为什么你需要有一个界面来告诉你这个类必须能够做什么,自从你在接口规范中编写它之后你就​​不知道了吗?

我可以看到接口的唯一原因是在大型项目中,您希望能够在不知道如何构建类的情况下使用类。通过查看界面所需的内容,您可以了解如何使用它。

这让我想知道为什么我应该在项目中使用(或者如果我应该)接口,我将是唯一一个工作。我很确定它有更多的用途,我没有看到。

我从this questionthis vbforums post

中获取了大部分假设和解释

10 个答案:

答案 0 :(得分:4)

你是正确的,接口指定合同,但实现可能会有很大的不同。

简单示例:Java中的列表。 List是一个界面。两个常见的实现是ArrayList和LinkedList。每个行为都不同但尊重同一合同。我的意思是ArrayList有O(1)(常量)访问,而LinkedList有O(n)访问。

如果您还不了解O(1)和O(n)的含义,建议您查看Plain english explanation of Big O

即使您自己的代码(即不是或不是公共API的内容)执行此操作的原因是:

  • 促进单元测试:你可以模拟一个界面而你不能(或不能轻易)模拟一个类;和
  • 允许您稍后更改实现,而不会影响调用代码。

答案 1 :(得分:3)

当有两个类需要一起工作但应该尽可能地彼此解耦时,接口很有用。一个常见的例子是当您使用listenersmodel-view-controller设计模式连接模型和视图时。

例如,假设您有一个用户可以登录和注销的GUI应用程序。当用户注销时,您可能会更改“当前登录为某某某”标签并关闭所有可见的对话窗口。

现在,您拥有UserlogOut方法,每当调用logOut时,您希望所有这些事情都发生。一种方法是让logOut方法处理所有这些任务:

// Bad!
public void logOut() {
    userNameLabel.setText("Nobody is logged in");
    userProfileWindow.close();
}

这是不赞成的,因为您的User类现在与您的GUI紧密耦合。最好让User课程变得笨拙,而不是那么多。它不应该关闭userProfileWindow本身,而应该告诉userProfileWindow用户已经注销并让userProfileWindow做任何想做的事情(它想要自己关闭)。

执行此操作的方法是创建一个通用的UserListener接口,其方法loggedOut在用户注销时由User类调用。任何想知道用户何时登录并注销的人都将实施此界面。

public class User {
    // We'll keep a list of people who want to be notified about logouts. We don't know
    // who they are, and we don't care. Anybody who wants to be notified will be
    // notified.
    private static List<UserListener> listeners;

    public void addListener(UserListener listener) {
        listeners.add(listener);
    }

    // This will get called by... actually, the User class doesn't know who's calling
    // this or why. It might be a MainMenu object because the user selected the Log Out
    // option, or an InactivityTimer object that hasn't seen the mouse move in 15
    // minutes, who knows?
    public void logOut() {
        // Do whatever internal bookkeeping needs to be done.
        currentUser = null;

        // Now that the user is logged out, let everyone know!
        for (UserListener listener: listeners) {
            listener.loggedOut(this);
        }
    }
}

// Anybody who cares about logouts will implement this interface and call
// User.addListener.
public interface UserListener {
    // This is an abstract method. Each different type of listener will implement this
    // method and do whatever it is they need to do when the user logs out.
    void loggedOut(User user);
}

// Imagine this is a window that shows the user's name, password, e-mail address, etc.
// When the user logs out this window needs to take action, namely by closing itself so
// this information isn't viewable by other users. To get notified it implements the
// UserListener interface and registers itself with the User class. Now the User.logOut
// method will cause this window to close, even though the User.java source file has no
// mention whatsoever of UserProfileWindow.
public class UserProfileWindow implements UserListener {
    public UserProfileWindow() {
        // This is a good place to register ourselves as interested observers of logout
        // events.
        User.addListener(this);
    }

    // Here we provide our own implementation of the abstract loggedOut method.
    public void loggedOut(User user) {
        this.close();
    }
}

操作顺序如下:

  1. 应用程序启动,用户登录。她打开了她的UserProfileWindow
  2. UserProfileWindow将自己添加为UserListener
  3. 用户空闲,不会触摸键盘或鼠标15分钟。
  4. 想象中的InactivityTimer课程通知并致电User.logOut
  5. User.logOut更新模型,清除currentUser变量。现在,如果有人问,那么没有人登录。
  6. User.logOut遍历其侦听器列表,在每个侦听器上调用loggedOut()
  7. 调用UserProfileWindow的{​​{1}}方法,关闭窗口。
  8. 这很好,因为这个loggedOut()类对于谁需要了解注销事件一无所知。它不知道需要更新用户名标签,需要关闭配置文件窗口,这些都不是。如果稍后我们决定在用户​​注销时需要完成更多的事情,则根本不需要更改User类。

    因此,侦听器模式是接口超级有用的一个示例。接口都是关于解耦类,删除需要彼此交互但不应该在代码中彼此之间存在强关联的类之间的联系和依赖关系。

答案 2 :(得分:2)

  

但是我很难掌握的是为什么你需要有一个界面来告诉你这个类必须能够做什么,自从你在接口规范中编写它之后你就​​不知道了吗?

当您编写外部可用代码时也很好。在这种情况下,代码编写者不是Interface的用户。如果您要向用户提供库,则可能只想记录Interface,并允许Class根据上下文进行更改,或者在不更改Interface的情况下随时间演变。< / p>

答案 3 :(得分:1)

假设您正在编写一组实现枪支的类。你可能有一把手枪,一把步枪和一把机枪。然后,假设您决定以这样的方式使用这些类,即您希望对每个枪执行fire()操作。你可以这样做:

private Pistol p01;
private Pistol p02;
private Rifle  r01;
private MachineGun mg01;

public void fireAll() {
    p01.fire();
    p02.fire();
    r01.fire();
    mg01.fire();
}

这种情况很糟糕,因为如果你添加或删除枪支,你必须在几个地方改变代码。或者更糟糕的是,假设您希望能够在运行时添加和删除枪支:它变得更加困难。

让我们创建一个上面的每个枪将实现的接口,称之为Firearm。现在我们可以做到这一点。

private Firearm[] firearms;

public void fireAll() {
    for (int i = 0; i < firearms.length; ++i) {
        firearms[i].fire();
    }
}

这有助于改变一点点,你不是吗?

答案 4 :(得分:1)

假设您有两个班级Car和Gorilla。这两个类彼此无关。但是,让我们说你也有一个可以粉碎事物的课程。而不是定义一个方法来接受一个Car并粉碎它然后有一个单独的方法来接受Gorilla并粉碎它,你创建一个名为ICrushable的接口...

interface ICrushable
{
  void MakeCrushingSound();
}

现在你可以让你的汽车和你的大猩猩实现ICrushable和你的Car实现ICrushable,你的破碎机可以在ICrushable而不是汽车和大猩猩上运行......

public class Crusher
{
  public void Crush(ICrushable target)
  {
    target.MakeCrushingSound();
  }
}

public class Car : ICrushable
{
    public void MakeCrushingSound()
    {
        Console.WriteLine("Crunch!");
    }
}

public class Gorilla : ICrushable
{
    public void MakeCrushingSound()
    {
        Console.WriteLine("Squish!!");
    }
}

static void Main(string[] args)
{
  ICrushable c = new Car();      // get the ICrushable-ness of a Car
  ICrushable g = new Gorilla();  // get the ICrushable-ness of a Gorilla

  Crusher.Crush(c);
  Crusher.Crush(g);
}

和Viola!你有一个破碎机可以粉碎汽车并获得“紧缩!”并且可以粉碎大猩猩并获得“Squish!”。无需经历查找Cars和Gorillas之间的类型关系以及编译时类型检查(而不是运行时切换语句)的过程。

现在,考虑一些不那么愚蠢的东西......一个可以比较的类(IComparable)。该类将定义如何比较它的两种类型的东西。

每条评论:好的,让我们这样做,这样我们就可以对一系列大猩猩进行排序。首先,我们添加一些东西来排序,比如说重量(请忽略按权重排序大猩猩的可疑业务逻辑......这里不相关)。然后我们实施ICompararble ......

public class Gorilla : ICrushable, IComparable
{
    public int Weight
    {
        get;
        set;
    }

    public void MakeCrushingSound()
    {
        Console.WriteLine("Squish!!");
    }

    public int CompareTo(object obj)
    {
        if (!(obj is Gorilla))
        {
            throw (new ArgumentException());
        }

        var lhs = this;
        var rhs = obj as Gorilla;

        return (lhs.Weight.CompareTo(rhs.Weight));
    }

}

请注意,我们“已经”解决了许多语言对single inheritance的限制。我们被允许实现尽可能多的接口。现在,通过这样做,我们可以使用10年前写的功能,这些功能是我今天刚刚写的一个类(Array.SortArray.BinarySearch)。我们现在可以编写以下代码......

var gorillas = new Gorilla[] { new Gorilla() { Weight = 900 },
                               new Gorilla() { Weight = 800 },
                               new Gorilla() { Weight = 850 }
};

Array.Sort(gorillas);
var res = Array.BinarySearch(gorillas, 
    new Gorilla() { Weight = 850 });

我的大猩猩得到排序,二元搜索找到匹配的大猩猩,重量为850。

答案 5 :(得分:0)

如果您想重新访问旧代码,您会感谢自己为自己构建了一些接口。没有什么比想要实现存在的新类型更令人沮丧,只是意识到你不记得新对象必须有什么。

在Java中,您可以实现多个接口,这些接口模拟多重继承(具有多个父对象的对象)。你只能扩展一个超类。

答案 6 :(得分:0)

没有人强迫你编写界面,甚至没有语言强制执行。这是一个优秀的程序员会遵循的最佳实践和习惯用语。你是唯一一个使用你的代码的人,你可以写下你喜欢的内容,但如果你离开项目而其他人必须维护和/或扩展它呢?或者,如果其他一些项目考虑使用您的代码呢?或者甚至在一段时间后,您还需要重新访问代码以添加功能或重构?你会为这些事情制造一场噩梦。很难理解你的对象关系和合同是什么建立的。

答案 7 :(得分:0)

抽象: 编写为使用接口的代码是可重用的,永远不需要改变。在下面的例子中,sub将使用System.Array,System.ArrayList,System.Collection.CollectionBase,List of T,因为它们都实现了IList。即使类继承了另一个类,现有的类也可以轻松实现接口。 你甚至可以编写你的类来在sub中为我们实现IList。或者另一个程序也可以实现在子中使用的接口。

public sub DoSomething(byval值为IList) 结束子

您也可以在一个类中使用多个接口,因此一个类可以是IList和IEnumerable,在大多数语言中,您可以继承一个类。

我还会看看它们是如何在各种框架中使用的。

答案 8 :(得分:0)

据我所知,为什么我们需要接口?对 ? 好吧,我们不需要它们:))

例如,在C ++中,当你定义一个模板时...说一个看起来像::

的虚函数
template <typename T>
void fun(const T& anObjectOfAnyType)
{
    anyThing.anyFunction();
}

你可以在任何具有任何名为anyFunction的函数的类型中使用此函数... 编译器唯一要做的就是用类型的名称替换T, 并编译新的代码......

事实上这非常容易出错。原因是如果我们插入一个没有anyFunction的类型,那么我们将得到一个错误,每次错误都不同, 编译器无法翻译的每一行都会为它发出错误。你只会错过很多错误! 例如,新类型没有必要的功能来正常使用我们的乐趣。

现在接口解决了整个问题,怎么样? 如果类型具有所需的函数,那么它是合适的,如果没有,那么编译器将发出该类型不适合的错误。

模板示例仅用于说明,如果您想要对没有接口的情况进行成像,那么您唯一需要做的就是在每个类中手动检查每个函数是否存在假设该类实现了特定的功能。脏工作由编译器完成:)

谢谢,

答案 9 :(得分:0)

接口减少了客户端所依赖的内容(http://en.wikipedia.org/wiki/Dependency_inversion_principle)。它允许多个实现,并能够在运行时更改实现。