How can you explain the difference between an interface and an abstract class to a non-programmer?
Possible Duplicate:
When to use an interface instead of an abstract class and vice versa?
嗨,我在教非程序员OOP概念。我想知道如何解释接口和抽象类之间的区别。我实际上正在寻找的是一个现实世界的例子,它可以帮助突出两者之间的区别。
播放器接口
在我的Java课程中,我经常使用MGXY1 0类图像并问:"这是什么?"
每次有人说"那是一个球员"。从这个图像,你可以教任何人界面是什么。这个界面允许任何用户"玩"一些东西。每个人都知道这些按钮是什么意思,即使你不知道到底要做什么,你也可以使用这个界面上的任何东西,你知道小箭头会"播放",其他箭头可能会让你前进或后退。所有拥有这些按钮的东西都将提供一个标准的行为,任何用户在开始使用它们之前都会知道。
我通常尽量避免用"合同"这个词,这可能会被误解。
DVD播放器抽象类然后从播放界面,我进入VCR(或DVD)播放器。DVD播放机的每个构造都必须添加一些特殊功能,以将简单的未知播放机转换为DVD播放机。例如,弹出按钮。他们必须正确执行播放器。"播放"按钮将启动DVD的内容。
但是,即使DVD播放器提供了DVD播放器的基本行为,也不是所有的事情都完成了。你不能简单地拥有"A"DVD播放器,它有一个品牌,而且大多数时候它有自己的固件。A这次您需要扩展DVD播放器抽象类来添加您自己的小组件。
下面是两个很好的比较:接口类和抽象类。我从下面复制了一个特定的示例:
界面Interfaces are often used to describe the peripheral abilities of a class, not its central identity, e.g. An Automobile class might implement the Recyclable interface, which could apply to many otherwise totally unrelated objects.
抽象类
An abstract class defines the core identity of its descendants. If you defined a Dog abstract class then Dalmatian descendants are Dogs, they are not merely dogable. Implemented interfaces enumerate the general things a class can do, not the things a class is.
界面
接口只是一个规范。它描述了必须做的事情。不多不少。就其本身而言,这是毫无意义的。只有当有人接受并实现该规范时,它才有用。
想想USB记忆棒。符合USB规格。与之通信的设备不需要知道或关心记忆棒是如何工作的,它只需要知道,当我们请求写入数据时,它是被写入的;相反,当我们请求从中读取数据时,我们期望接收到数据。
在计算方面,我们以同样的方式使用接口。如果我们有:
1 2 3 4 5 | public interface IUSB { Data Read(); bool Write(Data data); } |
我们知道实现这个接口的任何东西都必须为读写提供一个实现。它在幕后如何或做什么对我们来说无关紧要。通过在代码周围传递一个接口,我们不会将自己束缚在特定的实现上。
抽象类
抽象类只是为我们提供了一种方法,将规范放在派生类型必须实现的基类中,以及所有派生类型都可以使用的公共代码中。
我一直在尝试做一个好的现实世界的例子,并且一直在努力,所以只能真正想出一个代码示例。
假设您希望在代码中实现员工层次结构。所以你可能有:
1 2 3 4 5 6 7 8 | public abstract class Employee { public string FirstName { get; protected set; } public string LastName { get; protected set; } public string Grade { get; protected set; } public int Salary { get; protected set; } public abstract void GivePayRise(); } |
每个员工都有一个名字和一个相关的工作等级。我们可以在具有前3个属性的基类中对此进行建模。然而,发放奖金可能不是一件简单的事情,这取决于分数等。因此,我们将此标记为抽象。每个衍生类型的员工(兼职、全职、合同、顾问)都必须执行这一点。
实施可为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class FullTimeEmployee : Employee { public void GivePayRise() { Salary *= 1.1; } } public class PartTimeEmployee : Employee { public void GivePayRise() { Salary *= 1; } } |
所以我们想给全职员工10%的加薪,而不是给兼职员工。
很难给出好的例子——我通常倾向于使用接口,在过去一年左右的时间里,当我使用了一个抽象类时,我真的记不起来了。这可能会引发整个抽象类与接口的争论,但这是一个全新的页面……
抽象类:裁缝用来制作成衣尺寸的模板。虽然你不能穿模板本身,它是用来生产西装,你可以穿-西装是"衍生"从模板。
接口:着装规范。
界面:遥控器按钮。用户知道这些按钮应该如何工作。
具体类:东芝RC、飞利浦RC、JVC RC——盒子里的是具体的实现。
对于所有与计算机相关的东西,我都使用烹饪晚餐的例子。我首先说的是硬盘是橱柜/储物柜。记忆就像你的计数器。处理器是烹饪设备(炉子)。你就像系统总线(移动物体等)。所以当你启动一台电脑时,你把你的基本原料从仓库里拿出来放在柜台上(加载操作系统)。这是一个松散的例子,但它运行得很好。
现在进入OOP:配料是一个物体,工具也是(碗、刀、勺等)。每一个都有属性(刀子=刀柄颜色:黑色,刀刃类型:锯齿状等等)。每一个都有你可以用它们来执行的方法/动作(刀=切(胡椒)。
现在你可以随心所欲了。例如,有绿、黄、红辣椒。每一个都是胡椒,所以你可以说"继承胡椒类"(外行:把你所知道的所有关于胡椒的知识应用到这个特定的胡椒上,胡椒有颜色属性,红胡椒是颜色=红色)。
您甚至可以将类与实例分开(这个特定的Pepper是一个实例,而在食谱卡上它是一个类)。
所以你可以做一些伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | class pepper { var color var spiciness var size } class redPepper extends pepper { construct(){ $this->color=red } } class cuttingKnife extends knife{ construct(){ $this->blade_type=serated } } class cookingPot extends pot{ construct(){ //We all know what a cooking pot is } } class stove extends apparatus{ construct(){ //We all know what a stove is } } $knife = new cuttingKnife(); $myPepper = new redPepper(); $pot = new cookingPot(); $stove = new stove(); $knife->cut($myPepper); $pot->putOn($stove); $stove->cookOn("high"); $pot->putIn("water"); $pot->putIn($myPepper); //This will boil a cut pepper |
当然,人们不一定理解伪代码,但他们会理解如何煮东西。他们会理解"胡椒"和"红胡椒"的区别。我认为你可以用这个比喻来比喻任何与电脑有关的小东西。
- 多线程:在炉子上添加更多的烧嘴,在一个厨房中添加另一个厨师。
- 多核拱门:增加第二个厨房
- 下载/安装软件:去商店,找到食物,带回家,存起来
- 对HDD进行分区:不同的文件柜/冰箱可能是Linux进程系统(因为它是特殊的)。
等。。。
非常简单地说,一个接口定义了如何与我交谈。
而抽象课可以定义我的天赋之一,比如弹吉他。问题是"弹吉他"本身并没有那么有用。但是我们可以用这种能力创造一种类型的人,比如音乐家(我们可以说是一个班级)。
(在某些语言中,抽象类与接口的使用方式相同,因此可能会混淆)
少数有一个共同的接口的类就像一个可以填充一个句子中的空白的单词。例子:
- 有翅膀的
- 鸡有翅膀
- 空客A320有机翼
然而,阶级本身,虽然他们都能适应句子中的空白,但它们之间没有任何关系。鸡是一只鸡,而空中客车A320是一架飞机。唯一的共性是它们都有我们称之为"翅膀"的东西。(你也可以说"翅膀"的真正含义在这两种情况下是不同的。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class IHasWings : public IUnknown { public: // IUnknown methods: (Inherited) // IHasWings methods: virtual HRESULT GetWingSpan([out] double* pdblWingSpan) = 0; virtual HRESULT IsWingMovable([out] BOOL* pIsMovable) = 0; virtual HRESULT IsWingDetachable([out] BOOL* pIsDetachable) = 0; }; class Chicken : public ... ..., public IHasWings { }; class AirbusA320 : public ... ..., public IHasWings { }; |
计算器就是一个很好的例子。计算器内部是一块电路板,它的显示器、按钮和逻辑处理器之间有连接。
电路板就像一个抽象类。它为用它构建的任何计算器提供了管道。此外,它还有一些接口可以连接到显示器、按钮数组和逻辑处理器。
反过来,为与电路板配合使用而制造的任何显示器必须具有一个与电路板上的显示接口相匹配的连接器。按钮和逻辑处理器也是如此,后者可能具有与电路板上的接口对齐的特定插脚排列。
使用ood的开发人员将创建一个抽象类calculatorBase,来定义按钮、显示和内部逻辑之间的管道。抽象类还将指定派生类如何使用此管道来响应某些事件。
然而,CalculatorBase并不依赖于特定的显示、特定的按钮集,甚至是特定的逻辑实现。相反,开发人员为每个接口指定一个接口,例如iCalculatorDisplay。iCalculatorDisplay将指定CalculatorBase希望如何与显示交互。然后,CalculatorBase将与实现iCalculatorDisplay的任何显示一起工作。