java抽象类与接口的选择

选择Java接口还是抽象类

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

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

通过抽象类建立行为模型

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

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

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

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

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

代码
  1. public abstract Motor{
  2. abstract public int getHorsepower();
  3. }

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

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

  用Java方法来描述这个行为,代码如下:

代码
  1. public abstract BatteryPoweredMotor extends Motor{
  2. abstract public int getTimeToRecharge();
  3. }

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

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

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

代码
  1. public abstract SolarPoweredMotor extends Motor{
  2. abstract public int getLumensToOperate();
  3. }

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

代码
  1. if instanceof SolarPoweredMotor){...}
  2. if instanceof BatteryPoweredMotor){...}

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

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

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

通过接口建立行为模型

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

  行为1:

代码
  1. public interface Motor(){
  2. public int getHorsepower();
  3. }

行为2:

代码
  1. public interface BatteryPoweredMotor extends Motor(){
  2. public int getTimeToRecharge();
  3. }

行为3:

代码
  1. public interface SolarPoweredMotor extends Motor{
  2. abstract public int getLumensToOperate();
  3. }

  现在光电驱动的发动机可以描述为:

代码
  1. public DualPoweredMotor implements SolarPoweredMotor, BatteryPoweredMotor{}

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

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

Java接口特性学习 
在Java中看到接口,第一个想到的可能就是C++中的多重继承和Java中的另外一个关键字abstract。从另外一个角度实现多重继承是接口的功能之一,接口的存在可以使Java中的对象可以向上转型为多个基类型,并且和抽象类一样可以防止他人创建该类的对象,因为接口不允许创建对象。

interface关键字用来声明一个接口,它可以产生一个完全抽象的类,并且不提供任何具体实现。interface的特性整理如下:

1. 接口中的方法可以有参数列表和返回类型,但不能有任何方法体。

2. 接口中可以包含字段,但是会被隐式的声明为static和final。

3. 接口中的字段只是被存储在该接口的静态存储区域内,而不属于该接口。

4. 接口中的方法可以被声明为public或不声明,但结果都会按照public类型处理。

5. 当实现一个接口时,需要将被定义的方法声明为public类型的,否则为默认访问类型,Java编译器不允许这种情况。

6. 如果没有实现接口中所有方法,那么创建的仍然是一个接口。

7. 扩展一个接口来生成新的接口应使用关键字extends,实现一个接口使用implements。

interface在某些地方和abstract有相似的地方,但是采用哪种方式来声明类主要参照以下两点:

1. 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。

2. 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。

以上就是接口的基本特性和应用的领域,但是接口绝不仅仅如此,在Java语法结构中,接口可以被嵌套,既可以被某个类嵌套,也可以被接口嵌套。这在实际开发中可能应用的不多,但也是它的特性之一。需要注意的是,在实现某个接口时,并不需要实现嵌套在其内部的任何接口,而且,private接口不能在定义它的类之外被实现。

 

最后写点自己的认识:

      这个问题很重要,但是很多人都是工作很久了都是弄不清楚,为什么呢?这里不能直接把问题怪到开发者身上,很多人都是直接做的开发中很重要的二次开发或者外包或者是web的开发,所以很多东西都不需要自己去想,而且这样的工作基本上是很辛苦的,没有时间接触,知道了概念但是没有实践和真正的思考一切都是白搭,过不了3个月,又会忘记的。

      这个问题面试的时候也会很多人问起,有过设计方面的工作内容的人和没有过类似设计的人的回答还是有所不同的,再结合一些其他类似的问题,面试官假如有心的话还是能看出来一个人之前的工作内容的状态。

      中国的 软件行业大多数的人都是没有真正的学透一些语言,比如大多数人都是说语言只是一个工具罢了,有这样的想法的人更加不会想着去了解这个工具,只是会用就算了。也是因为没有需要,中国的公司就是这样了,你想太多也是没有什么用,因为工作不需要,你只要按照别人给的接口实现就可以了,没必要知道为什么要这样,到时候他自己会调用我这个接口的,或者我只要按照这种方式配置好就可以了,无所谓所谓的了解什么架构,了解什么思想。也许想太多。。。

 

 

最后再加上thinking in java中对接口使用原因最根本的指出:为了能够向上转型为多个基类型(以及由此而带来的灵活性)。

当然使用接口还有个原因是和抽象类相同的:防止客户端程序员创建该类的对象,并确保这仅仅是个建立一个接口。

 

所以:接口和抽象类,怎么选?当我们确定某个事物应该成为一个基类的话,接口是第一选择。

posted @ 2018-05-16 23:01  *Abby  阅读(2255)  评论(1编辑  收藏  举报