深入java(类)设计层次看抽象类和接口

上一篇文章说到抽象类和接口,总结抽象类和接口的基础知识点,同时解答为何会有这些知识点,例如:为什么Java的类只能单继承,接口倒是可以引用多个,为什么接口可以继承多个接口,为什么抽象的方法必须实现。这篇文章会在更高的角度出发,理解抽象类和接口。原创不易,转载请注明出处:http://blog.csdn.net/yabay2208

  • 抽象类和接口的区别
  • 从设计角度看抽象类和接口(举例子)

一:抽象类和接口的区别

前一篇文章只是从语法层次和编程角度来区分它们之间的关系,这些都是低层次的,要真正使用好抽象类和接口,我们就必须要从较高层次来区分了。只有从设计理念的角度才能看出它们的本质所在。一般来说他们存在如下三个不同点:

1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,所以由同一个抽象类继承出来的类属性和行为差别不会太大,但是接口却是对类局部(行为)进行抽象,而且这种行为是多种类都会具有,但是具体又不同的,具体的行为相差会很大。

2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可。但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is-a” 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。是”like-a”的关系

3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知,使用当前接口的类必须要实现对这个类的一套规范。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西(什么类)来实现这个飞接口,怎么实现(飞接口的具体实现)也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

二:从设计角度看抽象类和接口(举例子)

为了更好的理解抽象类和接口,我们下面将通过一个简单的实例进行说明。这个例子很多人都用过了哈,源于2002年,IBM developerWorks社区中邓辉 、孙鸣写的一个例子,尊重原作,我摘录如下:

假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用interface方式定义Door:
深入java(类)设计层次看抽象类和接口_第1张图片

使用abstract class方式定义Door:
深入java(类)设计层次看抽象类和接口_第2张图片

其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。


《第一种解决方案》:

简单的在Door的定义中增加一个alarm方法,如下:
深入java(类)设计层次看抽象类和接口_第3张图片
或者:
深入java(类)设计层次看抽象类和接口_第4张图片

那么具有报警功能的AlarmDoor的定义方式如下:
深入java(类)设计层次看抽象类和接口_第5张图片
或者:
深入java(类)设计层次看抽象类和接口_第6张图片
这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念”报警器”的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为”报警器”这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。


《第二种解决方案》:
我们可以分析:基本上所有的门都有open()和close()这两个方法,所以我们可以将open()和close()这两个方法设计在抽象类中,但是报警这种功能不仅仅只有门才会有,我们的其他一些类,比如汽车等,所以我们可以将这种可以高度自定义的行为设计成一个接口,如果我们实现了这个接口,只需要定义我们需要的自定义功能即可。
如果我们将所有方法都添加到一个抽象类中,会导致一些其他XXXDoor类不需要alarm()功能的门也必须实现这个功能,这样就侵入了我们将门抽象的基础
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是”is a”关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
深入java(类)设计层次看抽象类和接口_第7张图片
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是”is a”关系,interface表示的是”like a”关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

下一篇文章转载一下接口的默认修饰符的内容,同时添加设计模式的一个原则:ISP

你可能感兴趣的:(java深入理解)