答案 0 :(得分:72)
简单地说,而不是以一种说
的方式编写课程我依靠这个特定的课来完成我的工作
你用一种说法
来写它我依赖任何
第一个示例表示一个类,它依赖于执行其工作的特定具体实现。从本质上讲,这不是很灵活。
第二个示例表示写入接口的类。它并不关心你使用什么具体对象,只关心它实现了某些行为。这使得该类更加灵活,因为它可以提供任意数量的具体实现来完成其工作。
作为示例,特定类可能需要执行一些日志记录。如果您编写依赖于TextFileLogger的类,则该类将永远被强制将其日志记录写入文本文件。如果要更改日志记录的行为,则必须更改类本身。该类与其记录器紧密结合。
但是,如果您编写依赖于ILogger接口的类,然后为该类提供TextFileLogger,那么您将完成同样的事情,但具有更灵活的额外好处。您可以随意提供任何其他类型的ILogger,而无需更改类本身。该类及其记录器现在松散耦合,您的课程更加灵活。
答案 1 :(得分:19)
接口是相关方法的集合,仅包含这些方法的签名 - 而不是实际的实现
如果类实现了接口(class Car implements IDrivable
),则必须为接口中定义的所有签名提供代码。
基本示例:
你必须上课汽车和自行车。两者都实现了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
}
通过调用该接口方法,您不是编程实现而是编程接口 - 一种确保调用目标实现某种功能的合同。
使用继承可以实现相同的行为,但是从公共基类派生会导致紧密耦合,这可以使用接口来避免。
答案 2 :(得分:8)
现实世界的例子是苹果。其中之一:
对于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()
方法究竟是如何实现的,你知道它应该返回集合的大小 - 即你已经编程到{ {1}}界面,而不是Collection
和LinkedList
。
但我的建议是找一本书 - 或许是一本书(例如“思考用Java”) - 其中详细解释了这个概念。
答案 3 :(得分:8)
多态性取决于对接口的编程,而不是实现。
仅仅根据抽象类定义的接口来操作对象有两个好处:
这极大地减少了子系统之间的实现依赖性,从而导致了这种编程到接口的原则。
有关此设计的进一步说明,请参阅Factory Method pattern。
来源:G.O.F的“Design Patterns: Elements of Reusable Object-Oriented Software”
答案 4 :(得分:4)
每个对象都有一个暴露的界面。集合包含Add
,Remove
,At
等。套接字可能包含Send
,Receive
,Close
等等。
您实际可以获得引用的每个对象都具有这些接口的具体实现。
这两件事情都是显而易见的,但有些不太明显......
您的代码不应该依赖于对象的实现细节,只需要依赖于已发布的界面。
如果你采取极端的做法,你只需针对Collection<T>
进行编码,依此类推(而不是ArrayList<T>
)。更实际的是,只需确保您可以在不破坏代码的情况下交换概念相同的内容。
要敲定Collection<T>
示例:你有一些东西,你实际上使用的是ArrayList<T>
,因为为什么不。如果您将来最终使用LinkedList<T>
,则应确保代码不会中断。
答案 5 :(得分:3)
这也意味着,您不必关心您所依赖的代码的内部,只要界面保持不变,您的代码是安全的(好吧,或多或少......)
从技术上讲,有更精细的细节,例如Java中称为“接口”的语言概念。
如果您想了解更多信息,可以问“实施接口”是什么意思......
答案 6 :(得分:2)
我认为这是Erich Gamma的咒语之一。我在第一次描述它时(在GOF书之前)找不到,但你可以在接受采访时看到它:http://www.artima.com/lejava/articles/designprinciples.html
答案 7 :(得分:2)
它基本上意味着您将要使用的库的唯一部分应该依赖于它的API(应用程序编程接口),并且您不应该将您的应用程序建立在库的具体实现上。 / p>
例如。假设您有一个库,可以为您提供stack
。该课程为您提供了几种方法。我们说push
,pop
,isempty
和top
。您应该仅依靠这些来编写您的应用程序。违反这一点的一种方法是窥视内部并发现堆栈是使用某种类型的数组实现的,这样如果你从空堆栈弹出,你会得到某种索引异常然后捕获它而不是依赖于类提供的isempty
方法。如果库提供程序从使用数组切换到使用某种列表,前一种方法将失败,而后者仍然可以假设提供程序保持其API仍然有效。