`

The choise between Interface and Abstract Class

阅读更多
很多人有过这样的疑问:为什么有的地方必须使用接口而不是抽象类,而在另一些地方,又必须使用抽象类而不是接口呢?或者说,在考虑Java类的一般化问题时,很多人会在接口和抽象类之间犹豫不决,甚至随便选择一种。

  实际上接口和抽象类的选择不是随心所欲的。 要理解接口和抽象类的选择原则,有两个概念很重要:对象的行为和对象的实现。如果一个实体可以有多种实现方式,则在设计实体行为的描述方式时,应当达到这样一个目标:在使用实体的时候,无需详细了解实体行为的实现方式。也就是说,要把对象的行为和对象的实现分离开来。既然Java的接口和抽象类都可以定义不提供具体实现的方法,在分离对象的行为和对象的实现时,到底应该使用接口还是使用抽象类呢?

通过抽象类建立行为模型

  在接口和抽象类的选择上,必须遵守这样一个原则:行为模型应该总是通过接口而不是抽象类定义。为了说明其原因,下面试着通过抽象类建立行为模型,看看会出现什么问题。

  假设要为销售部门设计一个软件,这个软件包含一个“发动机”(Motor)实体。显然无法在发动机对象中详细地描述发动机的方方面面,只能描述某些对当前软件来说重要的特征。至于发动机的哪些特征是重要的,则要与用户(销售部门)交流才能确定。

  销售部门的人要求每一个发动机都有一个称为马力的参数。对于他们来说,这是惟一值得关心的参数。基于这一判断,可以把发动机的行为定义为以下行为。

  行为1:查询发动机的马力,发动机将返回一个表示马力的整数。

  虽然现在还不清楚发动机如何取得马力这个参数,但可以肯定发动机一定支持这个行为,而且这是所有发动机惟一值得关注的行为特征。这个行为特征既可以用接口定义,也可以用抽象类定义。为了说明用抽象类定义可能出现的问题,下面用抽象类建立发动机的行为模型,并用Java方法描述行为1,代码如下:
public abstract Motor{

abstract public int getHorsepower();

}

在Motor抽象类的基础上构造出多种具体实现,例如A型发动机、B型发动机等,再加上系统的其它部分,最后得到1.0版的软件并交付使用。一段时间过去了,现在要设计2.0版的软件。在评估2.0版软件需求的过程中,发现一小部分发动机是电池驱动的,而电池需要一定的充电时间。销售部门的人希望能够通过计算机查阅充电时间。根据这一要求定义一个新的行为,如图1所示。

  行为2:查询电驱动发动机的充电时间,发动机将返回一个表示充电时间的整数。

  用Java方法来描述这个行为,代码如下:
public abstract BatteryPoweredMotor extends Motor{
abstract public int getTimeToRecharge();
}

在销售部门的软件中,电驱动发动机也以类的形式实现,但这些类从BatteryPoweredMotor而不是Motor派生。这些改动加入到2.0版软件之后,销售部门很满意。随着业务的不断发展,不久之后光驱动的发动机出现了。销售部门要求光驱动发动机需要一定光能才能运转,光能以流明(Lumen)度量。这个信息对客户很重要,因为下雨或多云的天气里,某些光驱动发动机可能无法运转。销售部门要求为软件增加对光驱动发动机的支持,所以要定义一个新的行为。

  行为3:查询光驱动发动机能够正常运转所需要的最小流明数,发动机返回一个整数。

  再定义一个抽象类并把行为3转换成Java方法,代码如下:

public abstract SolarPoweredMotor extends Motor{

abstract public int getLumensToOperate();

}

如图1所示,SolarPoweredMotor和BatteryPoweredMotor都从Motor抽象类派生。在整个软件中,90%以上的代码以相同的方式对待所有的发动机。偶尔需要检查一下发动机是光驱动还是电驱动,使用instanceof实现,代码如下:
if (instanceof SolarPoweredMotor){...}

if (instanceof BatteryPoweredMotor){...}


无论是哪种发动机,马力这个参数都很重要,所以在所有派生的抽象类(SolarPoweredMotor和BatteryPoweredMotor)中,getHorsepower()方法都有效。

  现在销售部门又有了一种新的发动机,它是一种既有电驱动又有光驱动的双重驱动发动机。光驱动和电驱动的行为本身没有变化,但新的发动机同时支持两种行为。在考虑如何定义新型的光电驱动发动机时,接口和抽象类的差别开始显示出来了。新的目标是在增加新型发动机的前提下尽量少改动代码。因为与光驱动发动机、电驱动发动机有关的代码已经过全面的测试,不存在已知的Bug。为了增加光电驱动发动机,要定义一个新的SolarBatteryPowered抽象类。如果让SolarBatteryPowered从Motor抽象类派生,SolarBatteryPowered将不支持针对光驱动发动机和电驱动发动机的instanceof操作。也就是说,如果查询一个光电驱动的发动机是光驱动的,还是电驱动的,得到的答案是:都不是。

  如果让 SolarBatteryPowered从SolarPoweredMotor(或BatteryPoweredMotor)抽象类派生,类似的问题也会出现,SolarBatteryPowered将不支持针对BatteryPoweredMotor(或SolarPoweredMotor)的 instanceof操作。从行为上看,光电驱动的发动机必须同时从两个抽象类派生,但Java语言不允许多重继承。之所以会出现这个问题,根本的原因在于使用抽象类不仅意味着定义特定的行为,而且意味着定义实现的模式。也就是说,应该定义一个发动机如何获得行为的模型,而不仅仅是声明发动机具有某一个行为。

通过接口建立行为模型

  如果用接口来建立行为模型,就可以避免隐含地规定实现模式。例如,前面的几个行为改用接口定义如下。

  行为1:

public interface Motor(){

public int getHorsepower();

}
行为2:



public interface BatteryPoweredMotor extends Motor(){

public int getTimeToRecharge();

}



行为3:



public interface SolarPoweredMotor extends Motor{

abstract public int getLumensToOperate();

}
现在光电驱动的发动机可以描述为:
public DualPoweredMotor implements SolarPoweredMotor, BatteryPoweredMotor{}


  DualPoweredMotor只继承行为定义,而不是行为的实现模式,如图2所示。

  在使用接口的同时仍旧可以使用抽象类,不过这时抽象类的作用是实现行为,而不是定义行为。只要实现行为的类遵从接口定义,即使它改变了父抽象类,也不用改变其它代码与之交互的方式。特别是对于公用的实现代码,抽象类有它的优点。抽象类能够保证实现的层次关系,避免代码重复。然而,即使在使用抽象类的场合,也不要忽视通过接口定义行为模型的原则。从实践的角度来看,如果依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为能够更有效地分离行为与实现,为代码的维护和修改带来方便。
分享到:
评论

相关推荐

    前端 html + css + javascript(choise-card) .zip

    前端 html + css + javascript(choise-card) .zip

    Choise_exam_REST

    本项目"Choise_exam_REST"显然与使用Python实现RESTful API相关,可能是用于创建一个选择题考试系统。以下将详细介绍REST原则、Python在构建REST API中的应用以及可能涉及的相关技术。 1. REST原理: - **资源导向...

    SmartAdmin-html v1.9.0

    HTML version gives you the flexibility to select the platform ... Whether you wish to build this in .Net, PHP, Django, Java or anyother platforms out there - the HTML version should be the ideal choise!

    SmartAdmin-ajax v1.9.0

    HTML version gives you the flexibility to select the platform ... Whether you wish to build this in .Net, PHP, Django, Java or anyother platforms out there - the HTML version should be the ideal choise!

    day10图书编辑删除 字段参数choise(重要)多对多三种创建方式 ajax语法结构.pdf

    通过使用Q对象,可以灵活地在查询中使用AND、OR等逻辑运算符。 - **Q对象基础使用** - 使用`Q()`创建对象,可以实现多个条件的组合查询。 - 例如:`models.User.objects.filter(Q(title='三'), Q(price=11.11))`...

    Choice命令集(X86、X64)及dos常用命令大集合

    在Windows操作系统中,DOS(Disk Operating System)命令行是一个非常重要的工具,它允许用户通过命令行界面执行各种系统操作。Choice命令集是DOS环境下的一种特殊命令,用于在给定的一组选项中进行选择,通常用于...

    Python 关于反射和类的特殊成员方法

    class Dog(object): def __init__(self,name): self.name = name def eat(self): print("%s is eating..."%self.name) def run(): print("runing ....") d = Dog("lucy") choise=input("请输入要调用的方法:")...

    算法交易与套利交易

    网上唯一的全本,very good choise

    改进版贪吃蛇

    snake(int der ,int CountNode,int Speed,int FoodLife ,bool Snake_life,int choise); //snake类的构造函数 snake::snake(int der ,int CountNode,int Speed,int FoodLife ,bool Snake_life,int choise) { der=4; ...

    自定义Django Form中choicefield下拉菜单选取数据库内容实例

    class ObjectForm(forms.Form): pre = forms.ChoiceField(label="工作") def __init__(self, *args, **kwargs): super(ObjectForm, self).__init__(*args, **kwargs) self.fields["pre"].choices = get_object_...

    python实现简单购物商城

    if user_choise.isdigit() and int(user_choise) (shop_list): show_menu(user_choise) break else: print('\033[31;1m无效选项,请重新输入\033[1m') ``` - **退出与查询功能**:用户可通过特定按键退出或...

    FHICT:我在学校期间所做的事情

    个人FHICT回购欢迎我想欢迎您参加我的个人FHICT回购。...}B1[Display msg]B2[P1 makes choise]B3[CPU makes choise]C(End)C1{Is P1 = CPU?}C2[Tie]D{P1 = Rock?}D1{P1 = Paper?}D2{P1 = Scissors?}E

    组数.doc

    1. **选择排序(Choice Sort)**: 函数`choise`实现了选择排序算法。选择排序是一种简单直观的排序算法,它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待...

    视频选择引擎VCE.zip

    VCE = Video Choise Engine 这是一个很小的 JavaScript 库用来显示交互式视频操作,使用 JSON 描述交互的文本,提供多个分支和返回点。 示例 JSON: vce.init([{  video: "intro",  choices: [  {  label: ...

    万年历c++源代码

    - **循环与条件语句**:如 `while(1)` 循环和 `if(choise == 1 || choise == 2)` 条件判断等。 ### 3. 文件操作 - **文件读取**:使用 `fopen` 函数打开文件,并通过 `fgetc` 函数逐字符读取文件内容。 - **错误...

    TabletChoiceMobileApp

    Tablet Choise 应用程序旨在为餐厅和酒吧的员工和顾客提供便利。 该应用程序的想法是以移动应用程序的形式将以前使用的菜单替换为数字菜单。 通过该应用程序,客户应该能够查看菜单,从他们所在的餐厅订购。 顾客还...

    基于jQuery使用Ajax动态执行模糊查询功能

    var li = $('<li>', { class: 'list-group-item' }); li.text(item.name); // 假设item.name是返回的数据项 li.on('click', function() { // 处理点击事件,例如选择模板 }); resultList.append(li); }); },...

    C语言的atm银行存取系统

    puts("| Please select choise. |"); puts("| 1. Create a account. |"); /* 1 创建帐号 */ puts("| 2. To login system. |"); /* 2 登录帐号 */ puts("| 3. About system & help. |"); /* 3 关于系统 */ puts("| 4....

    c程序控制sqlite

    printf("Input your choise :"); scanf("%d",&i); switch (i) { case 1: Insert(); break; case 2: Delet(); break; case 3: Serch(); break; case 4: Change(); break; case 5: Print();...

    MQTT_examples_with_libmosquito:使用libmosquitto的CC ++中的MQTT订阅者和发布者示例

    选择了来实现C / C ++中的MQTT子订阅者和发布者代码,而是choise的MQTT代理,用于测试实现。 ###项目组件该项目包含三个MQTT客户端: mqtt_sub :环境数据主题的订阅者; mqtt_pub :虚拟/测试环境数据的发布者...

Global site tag (gtag.js) - Google Analytics