【问题标题】:Explaining Interfaces to Students [closed]向学生解释界面[关闭]
【发布时间】:2011-03-22 06:56:29
【问题描述】:

几年来,我曾担任编程模块简介的助教 - 一年级本科生的 Java。

大部分进展顺利,我们设法让学生很好地了解面向对象编程,但学生很少看到的一件事是接口。

我们给出的几乎所有解释都被认为过于做作而对学习无用,或者与他们作为初学者的立场相去甚远。我们往往得到的反应是“我……明白了”,翻译为“我不明白,它们听起来没用”。

这里有没有人可以成功地教学生有关接口的知识?我不再是助教了,但它总是对我唠叨。

【问题讨论】:

  • 我知道我在研究生院教一年级课程时遇到了这个问题。作为一个教学团队,我们决定,虽然我们想给他们这个想法并记住它,但一旦他们上了软件工程课程并且必须做一些“真正的”或大的事情——他们可以更清楚地看到代码重用的成果。
  • 作为一个你将教过的真正的本科生,我可以说,直到我们进入第二年并获得 SEG(和一个远程团队)的“快乐”,我才真正理解接口背后的意义。对我来说,我需要在一个团队中工作才能理解为什么需要接口。第一年年底的一个小组项目会比 SEG 早得多!
  • 不要在入门级课程中这样做。面向对象的开发是一个相当复杂的课题。 [与设计模式一起应该有自己的类]
  • @James:呵呵,小互联网。你是对的——当你最终不得不使用它们来完成你的工作时,接口就更有意义了。第一年教它的问题是,在那个级别上真的没有一个具体的、实际的例子,接口看起来很有用。与对象、集合、抽象类等不同,接口看起来毫无意义,您向它们展示的任何应用程序都显得太遥远而无用。
  • 这个问题似乎离题了,因为它不在帮助中心所述的讨论范围内。

标签: java interface


【解决方案1】:

在上一个问题中,有一些很好的场景可以解释使用接口背后的原因。

Stack Overflow Question

【讨论】:

    【解决方案2】:

    “类是什么,通常接口做某事。所以我可能有一辆汽车,但我永远不会去“carring”,但我可能会开车......所以我的 Car 可能实现“可驾驶”接口。”

    编辑:

    马克提出了一个很好的观点。接口根本不做任何事情,而是定义发生的行为。而且,他还提出了一个不想混淆观众的好观点。并不是说可以迷惑经验丰富的开发人员,但是迷惑一个全新的学生绝对不是一个好主意。鉴于此,我正在将我的单线修改为多线。

    “类定义存在,接口定义行为。类定义某物是什么,而接口定义某物做什么。所以我可能有一辆汽车,它有引擎之类的东西,它有多少汽油,它的历史MPG 是,等等,但我永远不会去“carring”。另一方面,我可能会去 Driving...我的 Car 可以驾驶吗?如果我给它一个 Drive 方法,它可以。我也可以有“Driveable” ” 与驾驶方法的接口,并让汽车来确定驾驶的真正含义。现在,如果我只有汽车,那么拥有接口并不是什么大不了的事。但是卡车呢?如果它们都是可驾驶的,我可以简单地为它们两个设置一个List<Drivable。当然,细心的学生说“为什么 Car 和 Truck 都不能简单地扩展 Vehicle,使用抽象的 Drive 方法?”这实际上是一个非常有效的概念。但是,航天飞机呢? Car 和 Truck 之间的组件很少适用于航天飞机,因此它似乎不太适合扩展 t车辆类。或者未来的汽车呢?我们不知道它们会是什么样子,它们可能没有底盘,它们可能只是让我们四处移动的能量气泡,但我们仍可能将它们的行为称为drive()。”

    呼吸

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

    【讨论】:

    • “接口做某事”可能会误导人们,因为接口不能有实现。也许“接口说明了可以做什么”?我没有很好的选择。
    • 一个有效的点,马克。我将根据这个见解进行修改。谢谢!
    • “一个类如何将已发布的交互列表与这些交互的默认实现相结合;接口仅列出交互。任何给定的类只能有一组事物的默认实现,因此可能只能从另一个类继承,但可以定义任意数量的交互。”?
    • @supercat - 这非常令人困惑。发布的交互列表?事物的默认实现集?他们只会盯着他看,就像他在说希腊语一样。
    • +1 对可能被误解的解释进行了出色的改写。
    【解决方案3】:

    接口提供了一个类需要做的事情,例如你可以有一个 Animal 接口,让我们说它有一个叫做 speak() 的方法,每个动物都可以说话,但它们都做的不同,但这允许你将任何实现动物的东西转换为动物,这样你就可以拥有一个动物列表并让它们都说话,但使用它们自己的实现。接口只是这些东西的包装器。

    【讨论】:

      【解决方案4】:

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

      至少,使用接口可以让您以某种方式/点从具体实现(负责行为的代码逻辑)进行更改,而无需重写整个代码。在解释事情时尝试使用现实世界的例子。这会更有意义。

      【讨论】:

        【解决方案5】:

        这解释得最好:(引用自tutorial

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

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

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

        【讨论】:

          【解决方案6】:

          如果您想向初学者解释它,我会坚持接口可以促进代码中的代码重用和模块化的想法:

          例如,假设我们要绘制一些对象:

          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 对象将需要具有绘制某些类型对象的方法,例如称为 paintFences()paintRocks() 的方法,我们需要有一个新的 @987654324 @ 对于我们希望画家能够绘制的每种类型的对象。

          但值得庆幸的是,我们有让绘制对象变得轻而易举的接口,而对象的绘制方式完全取决于实现 Paintable 接口的类。

          编辑

          我忘记提及的另一个好处是,如果您需要向代码库中添加新对象来绘画,您所需要做的就是创建一个实现 Paintable 的新类,并且 Painter 类永远不必更改。从这个意义上说,Painter 类从不依赖于它要绘制的对象,它只需要能够绘制它们。

          编辑 2

          James Raybould 让我想起了我忘记提及的接口的一个关键用途:在组件之间拥有接口,例如 Paintable 对象和 Painter 对象,可以让您更轻松地与其他人一起开发。一个开发人员可以处理 Painter 对象,而另一个开发人员可以处理 Paintable 对象,他们所要做的就是预先定义一个他们都将使用的通用接口。我知道当我在大学级别的项目中与其他人一起进行项目时,当你试图让每个人都在项目的不同部分工作并且最终仍然让所有组件很好地组合在一起时,它真的很有帮助。

          【讨论】:

          • 接口不应以“I”(Java 的编码约定)开头。
          • @Steve Kuo:说得好。我会更新我的帖子。
          • 但它可以 - eclipse 和 apache 的代码约定。
          • 嗯,无论哪种方式,这是另一个帖子的论据:)
          • 这是一个非常好的例子,并且可能会很好。不过,正如詹姆斯所说,我认为实际上,学生只需要遇到一个他们需要使用界面的非人为情况,然后界面的“为什么和在哪里”就会点击。
          【解决方案7】:

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

          USB 端口可以被视为一种规范,任何 USB 驱动器都可以放入其中,只要它们实现了该规范。因此,在这种情况下,端口变成了接口,并且可用的众多类型的 USB 记忆棒变成了类。 继续这个例子,如果我要为某人提供一个 USB 驱动器(类),我不需要告诉他们(调用方法)我正在传递什么。如果调用方法以 USB 驱动器(类类型)作为参考,我将无法传递任何东西,只能传递端口所指的 USB 驱动器。

          总而言之,接口可以帮助调用者与调用方法兼容(在调用方法需要特定类型的实例的用例中),无论您传递什么实例,调用者也是如此因为被调用者确信它(实例)将适合接口参考(类比的 USB 端口)。

          【讨论】:

          • 我喜欢这个解释。这不仅是一个很好的类比,而且“接口”可以与硬件一起使用而不会造成任何混淆。
          【解决方案8】:

          接口的真正价值在于能够覆盖 3rd 方 API 或框架中的组件。我会构建一个作业,学生需要覆盖他们无法更改(并且没有源)的预建库中的功能。

          更具体地说,假设您有一个“框架”,它生成一个实现为 Page 类的 HTML 页面。 page.render(stream) 生成 html。假设 Page 采用 sealed ButtonTemplate 类的实例。 ButtonTemplate 对象有自己的渲染方法,因此在 page.render(stream) buttonTemplate.render(label,stream) 在任何有按钮的地方都会被调用,它会为提交按钮生成 html。给学生举个例子,假设我们想用链接替换那些提交按钮。

          除了描述最终输出之外,我不会给他们太多指导。他们将不得不努力尝试各种解决方案。 “我们是否应该尝试解析出按钮标签并用锚标签替换?我们可以将 ButtonTemplate 子类化为我们想要的吗?哦,等等。它被密封了!当他们密封这个类时他们在想什么!?!“ 然后在分配之后显示第二个框架,该框架带有带有 render(label,stream) 方法的 ILabeledTemplate 接口。

          【讨论】:

          • sealed?我想你的意思是final。这是我们正在谈论的Java:P
          【解决方案9】:

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

          【讨论】:

          • 所以你让这个班级板着脸发誓它会提供界面中列出的功能和字段? :P
          • @RCIX,请重新阅读问题。这是一个教学情境——学生可以想象并更好地理解真正的概念。你的抽象感在哪里?
          【解决方案10】:

          除了其他答案,您可以尝试从不同的角度解释它。我相信学生们已经知道继承了,因为可能从第一堂课开始,每个 Java 学生都被它卡住了。他们听说过多重继承吗?方法解析在 C++(以及 Perl 和其他多继承语言)中被视为设计问题,因为从概念上讲,当在其两个基类中定义的子类中调用方法时究竟应该发生什么是模棱两可的。都被执行了吗?哪个先走?可以具体引用吗?另请参阅diamond problem。我的理解是通过引入没有实现的接口简单地解决了这种混淆,因此在方法解析期间使用哪个实现没有歧义。

          【讨论】:

            【解决方案11】:

            如果一个类需要准确处理一个抽象功能,并且不需要继承任何其他类,则可以使用抽象类来公开功能,然后从中派生出真正的类。但是,请注意斜体字中的两项。接口使一个类可以表现为几种独立的抽象事物类型,即使该类是从另一个不表现为这些事物类型的类派生的。因此,接口满足了多重继承的主要用例之一,而没有多重继承带来的麻烦。

            一个非常实用的接口的简单真实示例:iEnumerable。如果一个类拥有任意数量的某种类型的项目,那么对于另一个类对所有这些项目采取行动而不必担心持有它们的对象的细节是非常有用的。如果“enumerableThing”是一个抽象类,那么从不是“enumerableThing”的东西派生的任何类的对象都不可能传递给期望 enumerableThing 的代码。由于任何类(包括派生类)都可以实现 enumerableThing 而无需考虑基类是否这样做,因此可以为任何类添加枚举能力。

            【讨论】:

              【解决方案12】:

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

              DVD 播放器、电视、有线电视盒、遥控器都是封装复杂和精密功能的对象。但是,它们彼此之间以及与操作它们的人类之间都有接口,这在很大程度上隐藏了这种复杂性的最大份额。

              电视的视频插孔是由 DVD 播放器和有线电视盒以及许多其他类型的设备实现的接口。

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

              【讨论】:

              • 我认为这是所有答案中最好的真实示例。我希望我能投票两次:-)
              • +1:在软件中使用“接口”这个词在历史上甚至可能源自它在硬件中的使用。
              【解决方案13】:

              为此我推荐Head First Design Patterns的第一章。 Duck 模拟解释了使用继承的问题,本章的其余部分继续解释如何做到这一点。

              【讨论】:

              • +1 在设计模式中使用接口是我理解接口的关键突破。
              【解决方案14】:

              好吧,我刚刚向一个工作伙伴解释了接口,她正在从进步中学习 java,而且她一开始并没有掌握所有 OOP 的东西,所以我只是从非软件工程的角度解释了一切,我的解释是像这样的接口:

              假设您想聘请水管工来修理房子的一些东西,您不知道(而且您并不在乎)最终可能会雇用谁,但您知道水管工必须能够做什么。因此,您定义了一组任务,任何声称是管道工的人都必须知道如何去做。当然,每个人都可能有自己的方式来执行每项任务,但归根结底,你雇用的人是水管工,因为他们知道如何完成每项任务。所以,如果你要用 java 写这个,首先要做的是定义一个interface 管道工,如下所示:

              public interface Plumber
              { //silly code here }
              

              好吧,假设我知道如何完成您要求的每项任务,因此我完全符合您的要求,因此根据您的说法,我是一名水管工。所以,今天我决定成为你的管道工,你决定雇用我(耶!),根据最后一个例子,你可以说我是一个知道如何以特定方式开发软件和管道的人,如果我作为一个班级为我写代码,我可以写这样的东西:

              public class Rick extends Person implements SoftwareDeveloper, Plumber
              

              以后你可以用我当你的水管工来修理你家里的东西:

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

              从现在开始,剩下的 OOP 概念就很容易解释了。

              【讨论】:

              • 我不太喜欢这个答案,因为Person 对象是唯一可能成为PlumberSoftwareDeveloper 的对象。如果以这种方式呈现,它会模糊类/继承和接口之间的界限。
              【解决方案15】:

              理解接口与理解多态性和 IS-A 关系没有太大区别。由于通过实现接口或继承基类建立的关系,所有实现相同接口的类都可以由程序作为“基”类型统一操作。

              接口和基类之间的选择是一个设计决定。我会保持这个简单。

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

              接口和继承的好处几乎相同。接口只是比基类更抽象的类型定义。

              更新

              这是一个简单的程序,您可以使用它来演示继承和接口的相似之处。修改程序以使 Base 成为接口而不是类。在 ClassA 中,将“extends”替换为“implements”。该程序的结果将是相同的。

              ClassB 的目的是为了进一步说明一个类与其接口/基类之间关系的重要性。尽管 ClassB 的实例与 Base 相似,但它可能不会传递给 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());
                }   
              }
              

              【讨论】:

              • 这个答案在技术上是合理的,但在其他方面是垃圾。很多学生会被你的解释吓到。目标是使用学生已经理解的术语和概念来解释界面的含义。如果学生已经对多态性感到满意,那么学生可能不需要你的接口课程。
              • @emory,你不会明白在 Java 课程中继承可能在接口之前被覆盖(imo 应该)。一旦学生对继承进行了研究并理解了它,就应该使用该材料来教他们接口。如果他们不理解继承,他们就不会理解接口。因此,问题的一部分可能是学生在接触接口时对继承不够清楚。
              【解决方案16】:

              很久以前,我读了一本书(虽然不记得它的名字),它对接口有一个很好的类比。如果您(或您的学生)曾经去过 Cold Stone Creamery 冰淇淋店,这听起来有点熟悉。 Cold Stone 有冰淇淋,您可以使用冰淇淋在冰淇淋中添加几种不同的东西(在 Cold Stone 中称为 mix-ins)。这些混入将类似于接口。您的课程(或冰淇淋)可以有任意数量的接口(或混合)。添加一个接口(或混合)会将该接口(或混合)的内容(或风味)添加到您的类(或冰淇淋)中。希望这会有所帮助!

              【讨论】:

                【解决方案17】:

                契约是首先要学习接口的东西,但它们是用语言构建的,以提供多重继承的技能并避免多重继承的复杂性。所以你可以教他们接口将运行时行为添加到程序中,或者你可以告诉学生接口可以用来改变对象的运行时行为..

                【讨论】:

                  【解决方案18】:

                  首先,学生必须掌握抽象概念。 当你(你==学生)看到一位老师时,你可以将他描述为一位老师...... 您也可以将他描述为(学校的)雇员。 你可以把他描述为一个人。 你会是对的三次。这些是你可以给他的“头衔”。

                  他是一名教师,一名计算机科学教师,就像数学教师是一名教师一样。 它们处于同一抽象级别。 现在老师是雇员,就像看门人是雇员一样。 它们处于同一抽象级别。 雇员是人,就像失业者是人一样。 它们处于同一抽象级别。

                  (以一种 UML 的方式在板上画出整个东西)。

                  这就是(大致)描述科学教师在社会中的地位的架构。

                  现在抽象级别定义了一组共同​​对象的共同点:所有老师都教给他们的学生并创建不可能的考试问题以确保他们不及格。学校的所有雇员都为学校工作。

                  在编程中,接口是一个抽象层次。它描述了一组对象可以完成的动作。 每个对象都有独特的动作方式,但动作的类型是相同的。

                  以一些乐器为例:钢琴、吉他和长笛。 他们有什么共同点 ?音乐家可以演奏它们。 您不能要求音乐家吹奏这 3 种乐器,但可以请他演奏。

                  整个概念的架构如下:

                  接口(它们的共同点)是仪器。因为它们都是工具:这是它们共同的抽象。 他们有什么共同点?玩。因此,您定义了一个名为 Play 的抽象方法。

                  现在您无法定义“乐器”的演奏方式,因为它取决于乐器的类型。 长笛是一种乐器。因此,Flute 类实现了 Instrument。 现在你必须定义音乐家在演奏这种乐器时会做什么。 所以你定义了播放方法。此定义将覆盖仪器的定义。 对其他 2 种乐器执行相同操作。

                  现在,如果您有乐器列表但不知道它们是什么类型,您仍然可以“要求”它们演奏。 每一支长笛都会吹响。 每把吉他都会被划伤。 每架钢琴都会……嗯……钢琴?随便!

                  但是每个对象都知道如何执行“播放”动作。你不知道它们是什么乐器,但既然你知道它们是乐器,你就请他们弹奏,而他们知道该怎么做。

                  【讨论】:

                  • 我也可以很容易地链接到抽象类的解释。
                  【解决方案19】:

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

                  我们有很多对象...

                  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...)
                  

                  现在,有人正在创建(即)表并希望显示所有对象列表中的一些对象,但要显示列表中的对象,他必须编写返回 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());
                         }
                      }
                  }
                  

                  此外,我们可以以非常简洁有效的方式实现多个接口,无需任何派生(派生有时是不可能的,不仅在类已经使用某种其他派生的情况下)。

                  【讨论】:

                    【解决方案20】:

                    您可能还想比较和对比 Java 和 C++ 中的接口(最终使用多重继承和/或“朋友”类)。

                    (至少,对我来说,这向我展示了 Java 中的接口是多么简单/容易 :-)

                    【讨论】:

                      【解决方案21】:

                      我会告诉他们“接口定义提供什么行为”和“实现提供这些行为”。一段使用接口的代码不需要如何事情发生的细节,它只需要知道什么事情会发生。

                      DAO 模式就是一个很好的例子。它定义了诸如“保存”、“加载”、“删除”之类的行为。您可以有一个与数据库一起使用的实现,以及一个用于文件系统的实现。

                      我认为到目前为止,许多其他答案对于没有立即得到答案的学生来说太复杂了......

                      【讨论】:

                        【解决方案22】:

                        类,我们在最后几节课上实现了快速排序。很难按名称对 Person 列表进行排序。如果你必须按年级对这个列表进行排序,你现在会怎么做?如果你必须按年龄对恐龙列表进行排序,你会怎么做?到目前为止,您知道的唯一方法是复制快速排序的代码,并更改比较和它所操作的类型。这会起作用 - 直到你发现那个总是困扰你的快速排序的难以捉摸的错误,并且不得不在散布在各处的几十个快速排序副本中修复它。

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

                        我们可以编写一个快速排序,而无需定义我们想要对列表进行排序的顺序,并在调用该快速排序时单独定义该顺序(并且仅定义该顺序)。

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

                        现在,快速排序只有一个副本,错误只需修复一次。此外,人们可以在不理解它的情况下使用快速排序(或者如果他们理解了它,而在您想要对某些东西进行排序时不考虑它的机制)。此外,编写快速排序的人不需要知道您需要对列表进行排序的顺序。因此,界面隔离了两组程序员,允许他们独立开发自己的软件部分。这就是为什么在许多编程语言中,您会在 api 中找到实现良好且经过测试的排序方法,即使这些方法的程序员无法知道人们以后想要排序的所有对象类型和顺序。

                        【讨论】:

                        • 用标准库级别的功能来解释的好主意。学生可能已经熟悉这些对象,只需要向他们展示具体内容。我正在考虑的示例是使用 List 编写程序,以便您可以轻松地从 ArrayList 切换到 LinkedList 等...
                        【解决方案23】:

                        我认为总的来说,动手学习总是有助于在讲座和示例之后巩固概念。因此,正如 Meriton 所建议的那样,我将展示同一程序的两个版本。 (快速排序就是一个很好的例子)

                        让学生多次修改每个程序,揭示程序中的细微错误供他们修复。我认为,您的学生很快就会发现,当他们需要稍后修改程序时,界面在设计程序时提供了许多优势!

                        【讨论】:

                          【解决方案24】:

                          我一直认为这是尽可能少地(口头)沟通的一种手段,因为(良好的沟通)是软件工程中最困难的事情。 Web 服务和 SOA 也是如此。如果您给某人一个界面并说“请为我提供此服务”。这是一种非常方便的方法,因为您不必解释太多,编译器会检查他们是否做了正确的工作,而不是您! (我的意思是,不是真的,但至少它会确保方法在那里)。

                          【讨论】:

                            【解决方案25】:

                            面向接口的设计 比我以往任何时候都更好地描述了这一点 http://pragprog.com/titles/kpiod/interface-oriented-design。作者使用了一些很好的接口与继承的例子,比如动物王国的分类学。迄今为止,它有一些反对过度继承和明智使用接口的最佳论据。

                            一堆以不兼容的方式提出它们的网站:

                            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();
                                }
                            }
                            

                            哎呀,没有更多的代码?其实我们可以走得更远一点 让客户邀请几个朋友帮助它找到并渲染 请求的网站:

                            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,它完全耦合到特定的查找和基于渲染。当您访问数百或数千个站点时,将很难管理。有了 Website 界面,WebsiteFinder 可以轻松转换为支持地图、数据库甚至基于 Web 的查找,以满足不断扩大的规模。

                            接口可以将角色与实现它的组件分开。它们使得基于几乎任何东西交换相同问题的替代解决方案成为可能:

                            • 机器上的当前负载

                            • 数据集的大小(可以选择排序算法)

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

                            【讨论】:

                            • +1:我正在寻找这样的例子。
                            • 我很高兴它对你有用:)
                            【解决方案26】:

                            我输入这个作为对 Harima555 答案的评论,但它扩展了。我想知道从另一端开始是否更有意义 - 在了解如何编写接口之前,让他们了解接口的用途。

                            假设他们很好地掌握了继承、多态性和抽象类。我可能会先回顾一下抽象类,请一位学生解释它们。

                            接下来,介绍一个带有接口的类示例,以克服角色/契约的概念。为了简化事情,从一个超类开始。

                            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
                            

                            不要解释太多,但试着让学生理解语法可能意味着什么 - 可能会显示一些引用 Plumber 或 SoftwareDeveloper 的代码。

                            询问他们如何使用从 Person 继承来实现相同的目标。他们应该很快就会陷入困境,或者提出多重继承。为了避免稍后再讨论菱形问题,假设角色中没有重叠的方法。

                            接下来我会尝试克服相同的接口可以用于不同类型的类的想法。

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

                            再次尝试让他们考虑如何使用通用超类和覆盖来实现相同的东西。

                            然后说明您将如何使用接口而不是类来编写多态代码 - 比如说销售乘客运输车票的 TravelAgent。牢记这一点 - 您可以编写适用于不同层次结构的类的多态代码。

                            在这一点上,他们可能会认为接口很像能够将另一个超类添加到一个类中,并且已经掌握了多重继承的优势。

                            所以现在我们必须通过了解菱形问题来解释为什么这会使事情变得复杂,并且接口没有默认实现。

                            回到第一个示例,让他们了解如果 SoftwareDeveloper 和 Plumber 都有一个“MakeDrink”方法(一个制造可乐,另一个制造咖啡)并且我们在 Rick 上执行 MakeDrink,会发生什么。

                            尝试推动某人考虑如果 MakeDrink 在两个“超类”中都保持抽象,问题就会消失。至此,了解了概念方面的内容,我们应该准备好介绍定义接口的语法了。

                            (我确实考虑过引入第二个原因 - 编写可应用于不同类层次结构的通用代码的困难,但发现您最终会得到“为什么不能从接口继承高度属性”或过早讨论泛型编程)。

                            我认为现在我们应该已经通过米老鼠示例涵盖了这些概念 - 然后您可以返回解释正确的技术术语,并使用 Java API 中的真实示例。

                            • 我不想在人们尝试学习 Java/接口时让他们感到困惑,但是一旦他们掌握了知识,可能值得指出的是,其他 OO 语言对同一问题采取不同的方法,从多重继承鸭子打字 - 如果他们有兴趣,他们应该研究它们。

                            【讨论】:

                            • +1:这个答案应该比底部更好。
                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-08-09
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多