相反,你应该写出以下这样的类:我依赖于这个特定的类来完成我的工作。
第一个例子表示一个依赖于具体实现来完成任务的类。本质上来说,这并不是很灵活。我依赖于做这件事情的任何类来完成我的工作。
ILogger
示例的反例,我提供了 java.util.logging.Logger
和 org.apache.log4j.Logger
这两个非常流行的日志记录 API,它们都没有实现单独的接口,直接从客户端代码中使用。客户端代码仍然是“面向接口编程”,是 Logger
类的接口。 - Rogérioorg.apache.log4j.Logger
,那么您就不是“面向接口编程”。如果您编写的代码不关心提供的记录器是java.util.logging.Logger
、org.apache.log4j.Logger
还是任何其他公开通用接口的记录器实现,则您正在进行接口编程。 - Eric Kingnew
实例化一个实现类,也不要求每个类都有一个单独的接口。引用原书的话:“如果不存在适当的接口,则完全可以通过类来引用对象。” - Rogérioclass Car implements IDrivable
),则它必须为接口中定义的所有签名提供代码。
基本示例:interface IDrivable
{
void accelerate();
void brake();
}
class Car implements IDrivable
{
void accelerate()
{ System.out.println("Vroom"); }
void brake()
{ System.out.println("Queeeeek");}
}
class Bike implements IDrivable
{
void accelerate()
{ System.out.println("Rattle, Rattle, ..."); }
void brake()
{ System.out.println("..."); }
}
现在假设您有一个对象集合,它们都是“可驾驶的”(它们的类都实现了IDrivable接口):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()
方法:
for(IDrivable vehicle: vehicleList)
{
vehicle.accelerate(); //this could be a bike or a car, or anything that implements IDrivable
}
通过调用该接口方法,您不是针对实现编程,而是针对接口编程-这是一种确保调用目标实现某些功能的契约。
使用继承可以实现相同的行为,但从共同的基类派生会导致紧耦合,使用接口可以避免这种情况。
现实世界中有很多例子。其中之一:
对于JDBC,您正在使用接口 java.sql.Connection
。然而,每个JDBC驱动程序都提供了其自己的Connection
实现。您不必知道特定实现的任何信息,因为它符合Connection
接口。
另一个例子来自Java集合框架。有一个java.util.Collection
接口,它定义了size
、add
和remove
方法(以及许多其他方法)。因此,您可以交替使用各种类型的集合。假设您有以下内容:
public float calculateCoefficient(Collection collection) {
return collection.size() * something / somethingElse;
}
LinkedList
因为它对于其目的更有效率,而另一个使用了TreeSet
。LinkedList
和TreeSet
都实现了Collection
接口,你只需使用一个方法执行系数计算,不需要重复编写代码。size()
方法如何实现,只需要知道它应该返回集合的大小 - 也就是说你已经编程到了Collection
接口,而不是特定于LinkedList
和TreeSet
。每个对象都有一个公开的接口。集合有Add
、Remove
、At
等。套接字可能有Send
、Receive
、Close
等。
实际上您可以获取引用的每个对象都有这些接口的具体实现。
这两个事情是显而易见的,然而还有一件事情不太明显...
您的代码不应该依赖于对象的实现细节,而只是依赖于其公开的接口。
如果您采取极端措施,您只需编码使用 Collection<T>
等(而不是 ArrayList<T>
)。更为实用的方法是,确保您可以随时替换概念上相同的内容而不会破坏您的代码。
以 Collection<T>
为例:您有一些东西的集合,实际上您正在使用 ArrayList<T>
因为为什么不呢。您应该确保您的代码在未来使用 LinkedList<T>
等时不会崩溃。
我认为这是Erich Gamma的座右铭之一。我找不到他在GOF书之前第一次描述它的时间,但您可以在以下网址的访谈中看到它的讨论:http://www.artima.com/lejava/articles/designprinciples.html