向学生解释接口

几年来,我还是一位介绍编程模块的教学助理 – 一年级本科生的Java。

大多数情况下,它进行得很顺利,我们设法将面向对象的编程交给学生,但是学生们很less看到的一点是界面。

我们所做的任何解释都非常有意思,不适合学习,或者与初学者相距太远。 我们倾向于得到的反应是“我…看到”,翻译为“我不明白,他们听起来不是有用的”。

这里的任何人都有成功的方式来教学生的接口? 我不再是教学助理,但总是唠叨我。

如果你试图向初学者解释它,我会坚持这样一个观点:接口可以在代码中促进代码重用和模块化:

例如,让我们说我们要画一些对象:

public class Painter { private List<Paintable> paintableObjects; public Painter(){ paintableObjects = new ArrayList<Paintable>(); } public void paintAllObjects(){ for(Paintable paintable : paintableObjects){ paintable.paint(); } } } public interface Paintable { public void paint(); } 

现在你可以向学生解释,没有Paintable接口的Painter对象需要有方法来绘制某些types的对象,比如叫做paintFences()paintRocks() ,我们需要为每种types的我们希望画家能够画的对象。

但是幸运的是,我们有一些界面可以使绘画对象变得轻而易举,而且对象的绘制方式完全取决于实现Paintable界面的类。

编辑

另一个我忘记提到的好处是,如果你需要添加新的对象来绘制到你的代码库,所有你需要做的就是创build一个新的类来实现Paintable,而Painter类永远不会改变。 在这个意义上,画家类永远不依赖于它要绘制的对象,它只需要能够绘制它们。

编辑2

James Raybould提醒我关于我忘记提及的接口的一个关键使用:在组件之间有一个接口,比如Paintable对象和Painter对象,可以让你更容易地与其他人一起开发。 一个开发人员可以在Painter对象上工作,另一个开发人员可以在可绘制对象上工作,他们所要做的一切正常工作是定义一个通用接口,它们都将使用。 我知道什么时候在大学项目中与其他人共同完成项目时,如果要让每个人都在项目的不同部分工作,并且所有组成部分都能很好地结合在一起,那么这个项目是非常有用的。

在向非程序员解释接口和面向对象的概念时,我总是使用家庭娱乐系统的类比。

DVD播放机,电视机,有线电视盒,遥控器都是封装复杂和复杂function的对象。 然而,它们彼此之间以及操作它们的人类之间的接口在很大程度上隐藏了复杂性的大部分。

电视插孔中的video是由DVD播放器和电缆盒以及其他types的设备实现的接口。

我怀疑一个学生完全用Java代码来描述他们自己的家庭娱乐系统是可能的,也许是一个教育练习。

“在哪儿上课,通常都是接口,所以我可能会有一辆车,但我永远不会”驾车“,但我可能会开车…所以我的车可能会实现”可驱动“的界面。

编辑:

马克提出了一个好点。 接口根本不做任何事情,而是定义发生什么行为。 而且,他还提出了一个不想混淆听众的好话。 不是说混淆经验丰富的开发人员是可以的,但是混淆一个崭新的学生绝对不是一个好主意。 有鉴于此,我正在修改我的一行内容。

“在类定义存在的地方,接口定义了行为,类定义了什么是东西,接口定义了什么是东西,所以我可能有一辆汽车,它有像引擎,它有多less天然气,什么是历史的MPG,但我不会去“carring”,另一方面,我可能会去驾驶…我的车可以驱动吗?如果我给它一个驱动器的方法,我也可以有“Driveable”接口一个驾驶方法,然后把它放在车上,以确定什么样的驾驶真的意味着,现在,如果我只有汽车,有一个接口是没有什么大不了的,但卡车又是什么呢?如果他们都是Drivable,我可以简单的一个List<Drivable为他们两个当然, List<Drivable的学生说:“为什么卡车和卡车都不能简单地扩展车辆,用一个抽象的驱动方法?”这实际上是一个非常有效的概念。航天飞机?汽车和卡车之间的部件很less适用于航天飞机,所以它似乎不太适合延长太空飞船 e车辆级别。 或者未来的汽车呢? 我们不知道他们会是什么样子,他们可能没有机架,他们可能只是能量的泡沫,让我们感动,但我们仍然可以称他们的行为为drive()

吐气

现在这段/短文有点冗长,但是我可以看到,有些幻灯片或者黑板,对于一年级的学生来说很有效(假设他们首先理解抽象类)。

好吧,我只是向工作伙伴解释接口,她从进度中学习java,她一开始并没有得到所有的面向对象的东西,所以我只是从非软件工程的angular度来解释所有东西,喜欢这个:

假设你想雇用一名水pipe工来修理你房子里的一些东西,你不知道(你也不太在乎)你最终会雇用谁,但是你知道水pipe工必须做什么。 所以,你定义了一组任务,任何自称是水pipe工的人都必须知道该怎么做。 当然每个人都有自己的方式来执行每一项任务,但是最终你所雇用的人是一名水pipe工,因为他们知道如何完成每项任务。 所以,如果你要用java写这个,首先要做的是定义一个interfacepipe道工,像这样:

 public interface Plumber { //silly code here } 

那么好吧,让我们说,我知道如何做你所要求的每一个任务,所以我完全符合你的要求,所以根据你我是一个水pipe工。 所以,今天我决定成为你的水pipe工,你决定雇用我(耶!),根据最后一个例子,你可以说我是一个知道如何以特定方式开发软件和pipe道的人,如果我为我写代码作为一个类,我可以写这样的东西:

 public class Rick extends Person implements SoftwareDeveloper, Plumber 

后来你可以用你作为你的水pipe工来修理你房子里的东西:

 Plumber thePlumber = rick; thePlumber.fixLeak(myHouse.bathroom.leak) // =( 

从这一点来看,其余的面向对象的概念很容易解释。

好吧,最近我碰巧向某人解释了这一点。 我解释“为什么接口?”这个问题的方式是以USB端口和USB驱动器为例。

USB端口可以被认为是一个规范,任何USB驱动器都可以装入它,只要他们实现了规范。 所以在这种情况下,端口成为接口和众多types的USB棒可用,成为类。 提前这个例子,如果我要给某人一个USB驱动器(类),我不需要告诉他们(调用方法),我正在通过什么。 如果调用方法采用USB驱动器(类types)作为参考,我将不能够通过任何,但只有端口的USB驱动器的意思。

总而言之,Intefaces帮助调用者用调用方法(在调用方法需要某个特定types的实例的用例中)进行复合,而不pipe你传递哪个实例,调用者以及被调用者确定它(实例)适合接口参考(类比的USB端口)。

上课,我们花了最近几个会议实施快速sorting。 按姓名sorting人员名单很困难。 如果你必须按年级对这份名单进行分类,你现在会做什么? 如果你不得不按年龄分类恐龙名单,你会怎么做? 到目前为止,唯一的方法是复制快速sorting的代码,并更改比较和操作的types。 这样做会起作用 – 直到你发现总是困扰你的快速sorting的难以捉摸的bug,并且不得不将这个快速sorting的几十个副本修正到整个地方。

今天,我们要学习一个更好的方法。

我们可以编写一个快速sorting,而不需要定义我们想要sorting列表的顺序,并且当我们调用这个快速sorting时,分别定义这个顺序(以及唯一的顺序)。

[插入接口和多态的机制的解释,以比较器接口为例,这里]

现在,只有一个quicksort的副本,只能修复一次错误。 而且,人们可以在不理解它的情况下使用快速sorting(或者如果他们已经理解了它,而不想在什么时候想要sorting某些东西,那么就不必考虑它的机制)。 另外,编写quicksort的人不需要知道你需要sorting的列表。 所以这个接口隔离了两组程序员,允许他们孤立地开发他们的软件部分。 这就是为什么在许多编程语言中,你会发现在api中很好的实现和testing的sorting方法,即使这些方法的程序员不能知道所有types的对象和人们以后要sorting的命令。

我通常使用“合同”,但“郑重承诺提供”也可能有助于理解。

我为此推荐了Head First Design Pattern的第一章。 鸭子模拟解释了使用inheritance的问题,本章的其余部分继续解释如何去做。

这个解释最好:(从本教程引用)

软件工程中有许多情况,对于不同的程序员群体来说,同意一个“契约”来说明他们的软件如何相互作用是非常重要的。 每个组都应该能够编写自己的代码,而不需要知道如何编写其他组的代码。 一般来说,接口就是这样的合同。 例如,想象一个未来的社会,在这个社会中,电脑控制的机器人汽车在没有操作人员的情况下通过城市街道运送乘客。 汽车制造商编写运行汽车停止的软件(当然是Java),启动,加速,左转,等等。 另一个工业集团,电子制导仪器制造商,使计算机系统接收GPS(全球定位系统)的位置数据和无线传输的交通状况,并使用该信息来驱动汽车。

汽车制造商必须发布一个行业标准接口,详细说明可以调用什么方法使汽车行驶(任何汽车,任何制造商)。 然后指导制造商可以编写调用界面中描述的方法来指挥汽车的软件。 工业组织都不需要知道其他组织的软件是如何实施的。 实际上,每个组织都认为它的软件是高度专有的,并且保留随时修改它的权利,只要它继续遵守公布的界面。

更多链接: http : //download-llnw.oracle.com/javase/tutorial/java/concepts/interface.html

理解接口与理解多态和IS-A关系没有多大区别。 由于通过实现接口或inheritance基类build立的关系,实现相同接口的所有类可以被程序统一操纵为“基”types。

接口和基类之间的select是一个devise决定。 我会保持这个简单。

  • 定义一个类,当你的实现可以假设一个类的完整或部分行为。
  • 使该类的抽象表示基类不是一个完整的实现,不能按原样使用。
  • 如果提供部分实现没有意义,请提供接口而不是基类。

接口和inheritance的好处几乎相同。 接口只是一个比基类更抽象的types定义。

更新

这里有一个简单的程序,可以用来演示类似的inheritance和接口是如何的。 修改程序以使Base成为一个接口而不是一个类。 在ClassA中,将“implements”replace为“implements”。 程序的结果是一样的。

ClassB的目的是为了进一步说明一个类和它的接口/基类之间的关系的重要性。 除非我们build立明确的关系,否则尽pipe与Base相似,ClassB的实例可能不会传递给processBase。

 abstract class Base { public void behavior() {}; }; class ClassA extends Base { public void behavior() { System.out.println("ClassA implementation of Base behavior"); } }; class ClassB { public void behavior() { System.out.println("ClassB's version of behavior"); } } public class InterfaceExample { public void processBase (Base i) { i.behavior(); } public static void main (String args[]) { InterfaceExample example = new InterfaceExample(); example.processBase(new ClassA()); } } 

面向界面的devise描述这比我所能做的更好http://pragprog.com/titles/kpiod/interface-oriented-design 。 作者使用接口与inheritance的一些很好的例子,例如动物王国的分类。 它有一些反对过度inheritance和明智地使用我迄今为止读过的接口的最佳论据。

一堆网站提供不兼容的方式:

Facebook.java的清单:

 public class Facebook { public void showFacebook() { // ... } } 

YouTube.java的清单:

 public class YouTube { public void showYouTube() { // ... } } 

列表StackOverflow.java

 public class StackOverflow { public void showStackOverflow() { // ... } } 

客户端手动处理网站使用的各种不同方法:

ClientWithoutInterface.java的清单:

 public class ClientWithoutInterface { public static void main(String... args) { String websiteRequested = args[0]; if ("facebook".equals(websiteRequested)) { new Facebook().showFacebook(); } else if ("youtube".equals(websiteRequested)) { new YouTube().showYouTube(); } else if ("stackoverflow".equals(websiteRequested)) { new StackOverflow().showStackOverflow(); } } } 

引入网站界面,使客户的工作更轻松:

Website.java的清单:

 public interface Website { void showWebsite(); } 

Facebook.java的清单:

 public class Facebook implements Website { public void showWebsite() { // ... } } 

YouTube.java的清单:

 public class YouTube implements Website { public void showWebsite() { // ... } } 

列表StackOverflow.java

 public class StackOverflow implements Website { public void showWebsite() { // ... } } 

ClientWithInterface.java的清单:

 public class ClientWithInterface { public static void main(String... args) { String websiteRequested = args[0]; Website website; if ("facebook".equals(websiteRequested)) { website = new Facebook(); } else if ("youtube".equals(websiteRequested)) { website = new YouTube(); } else if ("stackoverflow".equals(websiteRequested)) { website = new StackOverflow(); } website.showWebsite(); } } 

呐喊,更多的代码没有? 事实上,我们可以进一步,让客户绳索几个朋友帮助它find并呈现一个请求的网站:

ClientWithALittleHelpFromFriends.java的列表:

 public class ClientWithALittleHelpFromFriends { public static void main(String... args) { WebsiteFinder finder = new WebsiteFinder(); WebsiteRenderer renderer = new WebsiteRenderer(); renderer.render(finder.findWebsite(args[0])); } } 

WebsiteFinder.java的清单:

 public class WebsiteFinder { public Website findWebsite(String websiteRequested) { if ("facebook".equals(websiteRequested)) { return new Facebook(); } else if ("youtube".equals(websiteRequested)) { return new YouTube(); } else if ("stackoverflow".equals(websiteRequested)) { return new StackOverflow(); } } } 

WebsiteRenderer.java的清单:

 public class WebsiteRenderer { public void render(Website website) { website.showWebsite(); } } 

回顾ClientWithoutInterface ,它完全耦合到特定的查找和基于渲染。 当您访问数百或数千个网站时,pipe理将会非常困难。 通过使用网站界面,可以轻松地将WebsiteFinder转换为在地图,数据库甚至基于Web的查找上进行备份以满足日益增长的规模。

通过接口,可以将angular色从实现它的组件中分离出来。 他们可以根据几乎任何东西来交换相同的问题的替代解决scheme:

  • 机器上的当前负载

  • 数据集的大小(可以挑选sortingalgorithm)

  • 请求正在执行的操作的用户

我打字这是作为评论Harima555s的答案,但它扩大了。 我想知道从另一端开始是否更有意义 – 让他们感觉接口是如何有用的,然后再讨论如何编写接口。

假设他们对inheritance,多态和抽象类有很好的把握。 我可能会首先回顾一下抽象类,通过让其中一个学生解释它们。

接下来,介绍一个带有接口的类的例子,以了解angular色/合同的概念。 为了简化,从一个超类开始。

 public class Rick extends Person implements SoftwareDeveloper, Plumber public class Zoe extends Person implements SoftwareDeveloper, Chef public class Paul extends Person implements Plumber, Chef public class Lisa extends Person implements Plumber 

不要太多地解释它,但是试着让学生去理解语法可能的含义 – 也许会显示一些引用水pipe工或软件开发者的代码。

问他们如何使用Personinheritance来实现同样的事情。 他们应该很快被卡住,或者想出多重inheritance。 为了避免在以后讨论钻石问题,假设在angular色中没有重叠的方法。

接下来,我试图克服这个想法,即可以在不同types的类上使用相同的接口。

 public class Plane extends Vehicle implements Fly, PassengerTransport, Serviceable public class Train extends Vehicle implements PassengerTransport, Serviceable public class Bird extends Animal implements Fly 

再次尝试让他们考虑如何使用公共的超类来实现相同的事物并重写。

然后说明如何使用接口而不是类来编写多态代码 – 比如说出售PassengerTransport票据的TravelAgent。 深入研究这一点 – 您可以编写适用于来自不同层次的类的多态代码。

在这一点上,他们应该可能会有这样的错觉:一个接口就像是能够为一个类添加另一个超类,并且将抓住多重inheritance的优点。

所以现在我们必须通过理解钻石问题来解释为什么这会使事情复杂化,并且接口没有默认的实现。

回到第一个例子,如果SoftwareDeveloper和Plumber都有一个“MakeDrink”方法(一个使Cola,另一个使Coffee),我们在Rick上执行MakeDrink,会发生什么事情。

尝试并推动某人思考如果MakeDrink在“超类”中保持抽象的问题就会消失。 在这一点上,从概念上讲,我们应该准备好覆盖定义接口的语法。

(我曾经考虑过介绍第二个原因 – 编写可应用于不同类层次结构的generics代码的难度,但是最终发现“为什么不能从接口inheritance高度属性”或讨论generics编程太早了)。

我想现在我们应该通过米老鼠的例子来介绍这些概念 – 然后你可以回过头来解释正确的技术术语,并使用Java API中的真实案例。

  • 在学习Java / Interfaces的时候,我不想迷惑人,但是一旦得到它,可能值得指出的是,其他的OO语言对于同一个问题采用不同的方法,从多inheritance到鸭 – 打字 – 如果他们感兴趣,他们应该研究它们。

你也教JDBC吗? 以此为例。 这是一个很好的现实世界的例子,如何强大的接口。 在JDBC中,您正在编写代码,这个API几乎只存在于接口中。 JDBC驱动程序是具体的实现。 您可以轻松地重复使用许多数据库上的JDBC代码,而无需重写代码。 您只需切换JDBC驱动程序实现JAR文件和驱动程序类名称即可使其在另一个数据库上工作。

至less,使用接口为您提供了以某种方式/点改变具体实现(负责行为的代码逻辑)而不用重写整个代码的可能性。 尝试在解释事物时使用现实世界的例子。 这会更有意义。

那么最近我发现了一个非常有用的使用接口的方法。

我们有很多对象…

 public class Settings { String[] keys; int values; } public class Car { Engine engine; int value; } public class Surface { int power; int elseInt; } // and maaany more (dozens...) 

现在,有人正在创build(即)表,并希望显示所有对象列表中的一些对象,但要显示列表中的对象,他必须编写返回String []的方法。

 String[] toArrayString() 

所以他只是在他需要的所有课堂上实施这个方法

 public class Settings { String[] keys; int values; public String[] toArrayString {...} } public class Car { Engine engine; int value; } // THIS NOT public class Surface { int power; int elseInt; public String[] toArrayString {...} } // and maaany more (dozens...) 

现在,当他创作桌子的时候,他就是这样写的

 public void createTable() { for(Object obj : allObjects) { if(obj instanceof Settings) { Settings settings = (Settings)obj; table.add(settings.toArrayString()); } if(obj instanceof Surface) { // cast... } // etc multiple times... } } 

通过接口,这个代码可以更短,更容易阅读和维护:

 public interface ISimpleInterface { String[] toArrayString; } public class Settings implements ISimpleInterface { String[] keys; int values; public String[] toArrayString {...} } public class Car { Engine engine; int value; } // THIS NOT public class Surface implements ISimpleInterface { int power; int elseInt; public String[] toArrayString {...} } public void createTable() { for(Object obj : allObjects) { if(obj instanceof ISimpleInterface) { ISimpleInterface simple = (ISimpleInterface)obj; table.add(simple.toArrayString()); } } } 

而且,我们可以以非常干净有效的方式实现多个接口,而不需要任何推导(派生有时是不可能的,而不是在类已经使用某种其他派生的情况下)。

接口提供了一个类需要做什么,例如你可以有一个动物接口,可以说有一个叫speak()的方法,每个动物都可以说话,但他们都做不同的事情,但这可以让你投什么实现动物对动物,所以你可以有一个动物列表,使他们都说话,但使用自己的实现。 接口只是这些东西的包装。

在前面的问题中,有一些很好的场景解释了使用接口的原因。

堆栈溢出问题

接口的真正价值在于能够覆盖第三方API或框架中的组件。 我会构build一个作业,学生需要覆盖预先构build的库中的function,而这些function是无法更改的(并且没有源代码)。

更具体地说,假设你有一个“框架”来生成一个实现为Page类的HTML页面。 而page.render(stream)生成的HTML。 假设Page是一个密封的 ButtonTemplate类的实例。 ButtonTemplate对象有自己的渲染方法,因此在page.render(stream)buttonTemplate.render(label,stream)被调用的地方有一个button,它产生一个提交button的html。 作为学生的一个例子,假设我们想用链接replace那些提交button。

除了描述最终的结果之外,我不会给他们太多的方向。 他们将不得不重新尝试各种解决scheme。 “我们是不是应该试图parsing掉button标签,并用锚定标签来代替?我们可以inheritanceButtonTemplate来做我们想做的事情吗?哦,等等,这是密封的!当他们封闭这个类时,他们在想什么! 然后在赋值之后,用render(label,stream)方法显示第二个带有ILabeledTemplate接口的框架。

除了其他的答案,你可以尝试从不同的angular度来解释它。 我相信学生们已经了解了inheritance问题,因为它可能从第一堂课挤在每个Java学生的喉咙里。 他们听说过多inheritance吗? 在C ++(以及Perl和其他多inheritance语言)中,方法解决被视为一个devise问题,因为从概念上讲,当在两个基类中定义的子类中调用某个方法时,会发生什么情况是不明确的。 都执行? 哪一个先走? 可以具体参考吗? 另见钻石问题 。 我的理解是,这个混淆只是通过引入接口来解决的,而这个接口没有实现,所以在方法parsing过程中使用哪个实现是没有歧义的。

如果一个类只需要处理一个抽象的function, 而不需要inheritance任何其他的类 ,就可以使用抽象类来公开function,然后从中派生出真正的类。 但是,注意斜体的两个项目。 接口使得类可以performance为多个独立types的抽象事物,即使这个类是从另一个不具有这些types的类的类派生的。 因此,接口满足了多重inheritance的主要用例之一,没有伴随多重inheritance的琐事。

一个非常实用的界面简单的现实世界的例子:iEnumerable。 如果一个类拥有一些任意数量的某种types的项目,对于另一个类来处理所有这些项目是非常有用的,而不必担心持有它们的对象的细节。 如果“enumerableThing”是一个抽象类,那么从不是“enumerableThing”的东西派生出来的任何类的对象将不可能被传递给需要enumerableThing的代码。 Since any class, including derived classes, can implement enumerableThing without regard for whether the base classes do so, it's possible to add enumeration ability to any class.

A long time ago, I read a book (can't remember the name of it though) and it had a pretty good analogy for interfaces. If you (or your students) ever went to a Cold Stone Creamery ice cream store, this will sound kind of familiar. Cold Stone has ice cream and with the ice cream you can add several different things to the ice cream (called mix-ins at Cold Stone). These mix-ins would be analogous to interfaces. Your class (or ice cream) can have as many interfaces (or mix-ins) as you want. Adding an interface (or mix-in) will add the contents (or flavor) of that interface (or mix-in) to your class (or ice cream). 希望这可以帮助!

Contracts are first things that are taught about interfaces but they are built in the language to provide the skills of multiple inheritance and avoid the complexity of multiple inheritance.. So you can teach them that interfaces add runtime behaviour to programs, or you can tell the students that interfaces can be used to change runtime behaviour of objects..

First, the students must grasp the concept of abstractions. When you (you == the students) see a teacher, you can describe him as a teacher… You can also describe him as an employe (of the school). And you can describe him as a person. You will be right the three times. Thoses are "titles" you can give him.

He is a teacher, a computer science teacher, in the same way a math teacher is a teacher. They are on the same level of abstraction. Now a teacher is an employee, in the same way a janitor is an employee. They are on the same level of abstraction. An employe is a person, in the same way an unemployed person is a person. They are on the same level of abstraction.

(Draw the whole thing on the board in a UML kinda way).

And that's the architecture that will describe (roughly) the position of a science teacher in society.

Now the levels of abstraction define what a common group of objects have in common : All the teachers teach to their students and create impossible exam questions to make sure they fail. All the school's employes work for the school.

In programming, an interface is a level of abstraction. It describes the actions that a group of objects can accomplish. Each object has a unique way of doing the action, but the type of action is the same.

Take a few music instruments for example : A piano, a guitar and a flute. What do they have in common ? A musician can play them. You can't ask a musician to blow in the 3 instruments but you can ask him to play them.

The architecture of the whole concept will be the following:

The Interface (what they have in common) is Instrument. Because they're all instruments : it's an abstraction they all have in common. What can they do in common ? 玩。 So you define an abstract method called Play.

Now you can't define how the "Instrument" will play because it depends on the type of instrument. Flute is a type of Instrument. So the class Flute implements Instrument. Now you must define what the musician will do when he plays that type of instrument. So you define the play method. This definition will override the definition of the Instrument. Do the same with the 2 others instruments.

Now if you have a list of instruments but don't know what type they are, you can still "ask" them to play. Each flute will be blown. Each guitar will be scratched. Each pianio will be … huh… pianoted ? whatever !

But each object will know what to do to execute the action "Play". You don't know what kind of instrument they are, but since you know they are instruments, you ask them to play and they know how to do that.

You may also want to compare and contrast interfaces in Java with C++ (where you end up using multiple inheritance and/or "friend" classes).

(At least, to me, that showed me how much simpler/easier interfaces were in Java 🙂

I would tell them "Interfaces define what behaviors are provided" and "Implementations provide those behaviors". A piece of code that uses an interface doesn't need the details of how things are happening, it only needs to know what things can happen.

A good example is the DAO pattern. It defines behavior like "save", "load", "delete". You could have an implementation that works with a DB, and an implementation that goes to the file system.

I think a lot of the other answers so far are too complicated for students who don't get it right away…

I think in general, hands-on learning always helps reinforce concepts after lectures and examples. So in the same vein as meriton suggests, I would present two versions of the same program. (quicksort is a good example)

Have the students modify each program several times, unveil subtle bugs in the program for them to fix. Your students will soon find, I think, that interfaces provide many advantages when designing a program when they're the ones who have to modify it later!

I always think of it as a mean to (verbally) communicate as little as possible because that (good communication) is the most difficult thing in software engineering. Same for Web Services and SOA. If you give somebody an interface and say "Please provide me this service." it is a very convenient way because you don't have to explain a lot, and the compiler will check if they did a proper job, instead of you! (I mean, not really but at least it'll ensure the methods are there).