很多人有过这样的疑问:为什么有的地方必须使用接口而不是抽象类,而在另一些地方,又必须使用抽象类而不是接口呢?或者说,在考虑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所示。
在使用接口的同时仍旧可以使用抽象类,不过这时抽象类的作用是实现行为,而不是定义行为。只要实现行为的类遵从接口定义,即使它改变了父抽象类,也不用改变其它代码与之交互的方式。特别是对于公用的实现代码,抽象类有它的优点。抽象类能够保证实现的层次关系,避免代码重复。然而,即使在使用抽象类的场合,也不要忽视通过接口定义行为模型的原则。从实践的角度来看,如果依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为能够更有效地分离行为与实现,为代码的维护和修改带来方便。
分享到:
相关推荐
**抽象工厂模式**是一种设计模式,属于对象创建型模式,它的主要目的是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。这种模式适用于当系统需要创建的对象属于多个产品等级结构,每个等级...
1.QNX screen系统是一个图形框架,提供开发时图像显示功能,抽象对外统一接口,屏蔽底层硬件差异。本资源利用screen图形接口,读取显示设备的显示内容,并将其以窗口形式同步显示,并用thread实时读取原窗口变化。...
数据库通用接口是软件开发中一个重要的概念,它允许程序员通过一套标准的接口来访问和操作各种不同的数据库管理系统,而无需关心具体的数据库实现细节。这样的接口能够提高代码的可复用性和降低开发难度,使得应用...
微型计算机原理与接口技术是计算机科学与技术领域中的核心课程之一,主要研究计算机硬件系统的基本构成、工作原理以及与...因此,这份“微型计算机原理与接口技术课件教案”是非常宝贵的教育资源,值得收藏和反复研读。
2. 对于Firefox和其他浏览器,由于没有直接的JavaScript接口实现收藏,一般会提示用户手动操作。例如: ```javascript if (!window.external || typeof window.external.AddFavorite != 'function') { alert('请按...
ADT 不直接暴露其内部实现细节,只对外提供接口,使得使用者可以根据需要调用相应操作,而无需关心数据是如何存储和操作的。例如,复数可以被定义为一个抽象数据类型,包含两个实数(表示实部和虚部),并定义了加减...
- 抽象类中可以同时包含抽象方法和非抽象方法,但是抽象方法不能声明为`private`。 - 间接继承抽象类的类可以不实现抽象类中的抽象方法,只要它仍然被声明为抽象类即可。 #### final关键字 - **final对象的特性*...
在帖子收藏模块中,两个主要实体是`Post`(帖子)和`UserFavorite`(用户收藏)。`Post`实体通常包含帖子ID、标题、内容、创建时间等属性。`UserFavorite`实体则包括用户ID、帖子ID以及收藏时间,用于记录用户收藏的...
综上所述,这道题目涵盖了Java面向对象编程的多个关键点,包括类与接口的定义、继承、抽象方法、访问控制、比较接口、以及数组和方法的使用,全面检验了考生对Java面向对象技术的理解和应用能力。
Java语言是面向对象编程的一种主要语言,其核心概念包括抽象、封装、继承和多态。在面向对象程序设计中,抽象和封装是两个基本特征。抽象是将现实世界中的复杂问题简化为可管理的模型,而封装则是隐藏对象的内部细节...
- 接口是Java中的一个特殊类型,它包含抽象方法(没有方法体的方法)和常量。接口不能实例化,即不能创建接口的对象,选项D正确。接口用于定义行为规范,类通过实现接口来继承接口中的所有抽象方法。实现接口的类...
5. **接口与抽象类的区别**: - **接口**定义行为规范,强制实现接口的类实现接口中所有的方法。接口不能包含方法的实现,只能包含方法、属性、事件和索引器的签名。 - **抽象类**可以包含已实现的方法和字段,...
Linux系统下的Framebuffer设备是操作系统提供的一种抽象接口,用于直接访问显示缓冲区,简化了对图形显示的处理。Framebuffer设备通常对应于/dev/fb*设备文件,最多可支持32个设备,如/dev/fb0到/dev/fb31。在嵌入式...
选项B和C正确描述了抽象方法与接口的特性,而选项D正确指出接口中的成员变量默认是常量。 4. 类型转换与继承:在Java中,子类是父类的实例,因此可以将子类对象赋值给父类引用。题目中,`Circle`是`GeometricObject...
相关主题多线程收藏Java 数据库连接 (JDBC)Java 程序Java 字符串方法雅加达服务器页面 (JSP)ServletJava 多项选择题Java 设计模式休眠Spring 框架基础目录介绍Java 架构Java 数据类型Java 方法Java 函数式编程Java ...
9. **接口与抽象类**:接口用于定义一组方法,但不提供实现,而抽象类可以包含抽象方法(没有实现的方法)和具体方法。两者都是实现多态的方式,但接口更强调行为的规范,而抽象类则更多关注部分实现。 10. **反射...
2. 面向对象:理解类与对象的概念,掌握构造函数、抽象类、接口、访问修饰符、重写和重载的区别。 3. 异常处理:异常的分类、捕获和抛出,理解finally块的作用。 二、Java内存管理 1. 内存区域:堆、栈、方法区、...