关于Java:”程序到接口”。这是什么意思?

“Program to an interface”. What does it mean?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
What does it mean to “program to an interface”?

我不断地遇到这个词:

Program to an interface.

这到底是什么意思?一个真实的设计场景将受到高度赞赏。


简单地说,而不是用一种说

I depend on this specific class to do my work

你的写作方式

I depend on any class that does this stuff to do my work.

第一个示例表示一个类,该类依赖于特定的具体实现来完成其工作。从本质上来说,这不是很灵活。

第二个示例表示写入接口的类。它不关心您使用的具体对象,只关心它实现了某些行为。这使得类更加灵活,因为可以为它提供任意数量的具体实现来完成它的工作。

例如,一个特定的类可能需要执行一些日志记录。如果您编写的类依赖于一个textfilelogger,那么该类将永远被强制将其日志记录写到一个文本文件中。如果要更改日志记录的行为,必须更改类本身。类与其记录器紧密耦合。

但是,如果您编写的类依赖于一个ilogger接口,然后为该类提供一个textfilelogger,那么您将完成相同的事情,但会带来更灵活的额外好处。您可以随意提供任何其他类型的ilogger,而不必更改类本身。类及其记录器现在是松散耦合的,您的类更灵活。


接口是相关方法的集合,它只包含这些方法的签名,而不是实际的实现。如果一个类实现了一个接口(class Car implements IDrivable),它必须为接口中定义的所有签名提供代码。

基本实例:你得把车和自行车分类。两者都实现接口idrivable:

1
2
3
4
5
interface IDrivable
{
    void accelerate();
    void brake();      
}
1
2
3
4
5
6
7
8
class Car implements IDrivable
{
   void accelerate()
   { System.out.println("Vroom"); }

   void brake()
   { System.out.println("Queeeeek");}
}
1
2
3
4
5
6
7
8
class Bike implements IDrivable
{
   void accelerate()
   { System.out.println("Rattle, Rattle, ..."); }

   void brake()
   { System.out.println("..."); }
}

现在假设您有一个对象集合,这些对象都是"可驱动的"(它们的类都实现了IDrivable):

1
2
3
4
5
6
7
List<IDrivable> vehicleList = new ArrayList<IDrivable>();
list.add(new Car());
list.add(new Car());
list.add(new Bike());
list.add(new Car());
list.add(new Bike());
list.add(new Bike());

如果现在要循环该集合,则可以依赖这样一个事实:集合中的每个对象都实现accelerate()

1
2
3
4
for(IDrivable vehicle: vehicleList)
{
  vehicle.accelerate(); //this could be a bike or a car, or anything that implements IDrivable
}

通过调用该接口方法,您不需要编程到实现,而是编程到接口——一个确保调用目标实现特定功能的契约。使用继承可以实现相同的行为,但从公共基类派生会导致紧密耦合,使用接口可以避免这种耦合。


多态性依赖于接口的编程,而不是实现。

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

  • 客户机仍然不知道他们使用的特定类型的对象,只要对象符合客户机期望的接口。
  • 客户机仍然不知道实现这些对象的类。客户机只知道定义接口的抽象类。
  • 这大大减少了子系统之间的实现依赖性,从而导致了编程到接口的原则。

    有关此设计的进一步推理,请参见工厂方法模式。

    资料来源:G.O.F.的"设计模式:可重用面向对象软件的元素"。

    另请参见:工厂模式。何时使用工厂方法?


    现实世界的例子是applenty。其中之一:

    对于JDBC,您使用的是接口java.sql.Connection。但是,每个JDBC驱动程序都提供自己的Connection实现。您不必了解任何关于特定实现的信息,因为它符合Connection接口。

    另一个是Java集合框架。有一个java.util.Collection接口,它定义了sizeaddremove方法(等等)。所以您可以互换使用所有类型的集合。假设您有以下内容:

    1
    2
    3
    public float calculateCoefficient(Collection collection) {
        return collection.size() * something / somethingElse;
    }

    以及其他两个调用此方法的方法。另一种方法使用LinkedList,因为它的目的更有效,而另一种方法使用TreeSet

    由于LinkedListTreeSet都实现了Collection接口,所以只能使用一种方法来进行系数计算。无需复制代码。

    这里有一个"程序到接口"—你不在乎size()方法的实现有多精确,你知道它应该返回集合的大小—也就是说,你已经编程到Collection接口,而不是LinkedListTreeSet接口。

    但我的建议是找到一个阅读-也许是一本书(例如,用Java思考)-在这个概念被详细解释。


    每个对象都有一个公开的接口。一个集合有addremoveAt等,一个套接字可以有SendReceiveClose等。

    您实际上可以得到的每个对象都有这些接口的具体实现。

    这两件事都是显而易见的,然而不那么明显的是…

    您的代码不应该依赖于对象的实现细节,只依赖于它发布的接口。

    如果你把它推向极端,你只需要对Collection等进行编码(而不是ArrayList)。实际上,只需确保在不破坏代码的情况下交换概念上相同的内容即可。

    敲出Collection的例子:你有一些东西的集合,你实际上使用了ArrayList,因为为什么没有。如果你在将来使用LinkedList,你应该确保你的代码不会被破坏。


    "编程到接口"发生在您使用库(您自己的代码中依赖的其他代码)时。然后,其他代码向您表示自身的方式、方法名、其参数、返回值等构成了您必须编程的接口。所以这是关于如何使用第三方代码的。

    这也意味着,你不必关心你所依赖的代码的内部,只要接口保持不变,你的代码就是安全的(好吧,或多或少…)

    从技术上讲,有更精细的细节,比如Java中所谓的"接口"的语言概念。

    如果您想了解更多信息,可以问"实现接口"是什么意思…


    它基本上意味着库中您将要使用的唯一部分应该依赖于它的API(应用程序编程接口),并且不应该将应用程序建立在库的具体实现之上。

    假设你有一个图书馆给你一个stack。这个类提供了两种方法。比如pushpopisemptytop。您应该只依赖这些来编写应用程序。违反这一点的一种方法是深入查看并发现堆栈是使用某种类型的数组实现的,这样,如果从空堆栈中弹出,您将得到某种索引异常,然后捕获它,而不是依赖类提供的isempty方法。如果库提供者从使用数组切换到使用某种列表,前者将失败,而后者将仍然工作,前提是提供者保持其API仍在工作。


    我想这是埃里希·伽玛的咒语之一。我找不到他第一次描述它(在GoF的书之前),但是你可以在以下的访谈中看到它的讨论:http://www.artima.com/lejava/articles/designprinciples.html