Java中接口和抽象类的比較
Posted liguangsunls
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中接口和抽象类的比較相关的知识,希望对你有一定的参考价值。
Java中接口和抽象类的比較-2013年5月写的读书笔记摘要
1. 概述
接口(Interface)和抽象类(abstract class)是 Java 语言中支持抽象类的两种机制,是Java程序设计使用多态性的基础[[1]]。
(在面向对象语言中,接口的多种不同的实现方式即为多态。
多态性是同意你将父对象设置成为和一个或很多其它的他的子对象的技术。赋值之后。父对象就能够依据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4编程技术内幕”)。
简单的说。就是一句话:同意将子类类型的指针赋值给父类类型的指针[[2]])。
在那些面向对象(object)的程序设计语言的概念中,类(classss)指的是一种抽象的数据类型、是客观对象在人脑中的主观反映、是对象共性的抽象、类型同样事物数据的抽象。
能够说,所有的对象都须要通过类来进行描写叙述,可是所有的类却不一定都是用来对对象来进行描写叙述的。
假设某一个类中所包括的信息不足以用来描写叙述一个详细的对象,那么我们就称其为抽象类(abstract class)。抽象类是我们在对某一问题领域进行设计和分析时所得出的抽象概念。是一系列本质上同样,而外在形象各异的详细概念的抽象反映[[3]]。
比方:假设我们进行一个图形编辑软件的开发。就会发现问题领域存在着圆、三角形这样一些详细概念,它们是不同的,可是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是由于抽象的概念在问题领域没有相应的详细概念,所以用以表示抽象概念的抽象类是不可以实例化对象的。
接口好比是程序之间的一个约定或合同,但它仅仅定义了行为的协议,并未定义履行接口协议的详细方法。接口中仅仅指定抽象方法的方法头。但不提供抽象方法的详细实现。接口定义的仅仅是实现某种特定功能的一组对外接口和规范,而其详细功能的实现是在实现这个接口的各个类中完毕的[1],一个类实现了某个接口,我们就说这个类符合了某个约定。
接口表示一种能力[4],一个类实现了某个接口。就表示这个类具备了某种能力。
生活中一个人能够具有多项能力,一个类也能够实现多个接口。
抽象类强调的是“概念”,接口强调的是“能力”(或者说是“行为”)。
2.Java中的接口和抽象类的相似之处
(1)抽象类和接口都能实现对一组行为的抽象。接口和抽象类都可包括抽象方法。继承(实现)它们的类实例必须所有实现它们定义的抽象方法后才干用于实例化对象(只是,抽象类能够一个抽象方法都没有);
(2)接口和抽象类木身都不能用于对象实例化,但它们的引用能够指向继承(实现)它们的类实例从而动态地使用这些类实例。
3. Java中的接口和抽象类的差别
1) 属性:抽象类中能够有变量,而接口中不能定义变量。即接口中属性都会自己主动用public static final来修饰,都是全局静态常量。且必须在定义时指定初始值。
2) 方法:抽象类中能够有详细方法和抽象方法,抽象类的子类也能够是抽象类;而接口中全部方法都是抽象方法。接口中方法都会自己主动用public abstract修饰。
因为接口不涉及实现。从这点上看,接口在抽象化程度方面比抽象类的更高。
3) 继承性:抽象类属于一种特殊的类。类的继承必须满足一个子类仅仅能有一个父类(单继承),但能够实现若干个接口(多实现)。而接口能够实现多继承,即一个接口能够有多个父接口。
4) 设计思想[1]: 这一点也是最本质的一点,对于抽象类与接口的选择非常重要。对于抽象类来说,抽象类与其子类之间存在一个“is a"的继承关系,即二者形成层次结构。父类和子类在概念本质上是同样的。
如Shape类与其子类Circle , Square ,Triangle在本质上是同样的,明显存在一个“is a"关系。即Circle(圆形)、Square(矩形),Triangle(三角形)都属于Shape形状。
对于接口来说.接口与实现它的类之间不存在仟何层次关系。
也不要求接口和实现它的类之间在概念本质上一致,仅仅要求接口的实现者实现接口规定的功能。接口能够实现毫不相关类的同样行为。比抽象类的使用更加方便灵活。比方定义一个接口open_close表示开关行为:
interface open_close{
void open();
void close();
}
门door有开关功能。能够让Door类实现open_close接口。
class Door implements open_close {
void open() {……}
void close() {……}
}
手机MobileTelephone有开关功能。能够让mobileTelephone类实现open_close接口。
class MobileTelephone implements open_close {
void open() {……}
void close() {……}
}
电脑computer有开关功能。能够让Computer类实现open_close接口。
class Computer implements open_close {
void open() {……}
void close() {……}
}
从该例能够看出,类Door, MobileTelephone,Computer与接口open_close之间不存在不论什么层次关系。它们之间是“has”关系,即Door, MobileTelephone,Computer都具有接口open_close指定的功能。通过接口open_close实现了毫不相关类Door, MobileTelephone,Computer的共同行为。
再举个不太精确,可是好理解的样例。在学校的学生信息管理软件中“我是一个能打篮球、踢足球的学生”能够定义为:
class Me extends Student implementsIPlayBasketeball, IPlayFootball { }
我是学生,因此我extends Student,我有打篮球,踢足球的能力。因此我实现IPlayBasketeball, IPlayFootball,我就拥有了IPlayBasketeball, IPlayFootball接口提供的各种行为(如。传球。头球,射门等)。而且我自己实现它们。
4.接口与抽象类的选择[4]
考虑这样一个样例,如果我们在为一个电器生产厂家开发手机软件。问题领域中有一个关于手机的抽象概念,该手机具一些动作如开机,关机等。此时我们能够通过接口或者抽象类来定义一个表示该抽象概念的类型,定义方式分别例如以下所看到的:
使用接口方式定义手机:
interface Mobile_phone{
void open();
void close();
}
使用抽象类方式定义手机:
abstract class Mobile_phone{
abstract void open();
abstract void close();
}
而详细的手机类型(如A18型,B30型)能够继承抽象类方式定义。或者使用实现接口的方式定义。看起来好像使用接口和抽象类没有大的差别。但随着技术的发展,假设如今要求手机要集成有信用卡的功能。我们应该怎样设计针对该样例的类结构呢?信用卡的一些基木功能有,电子钱包,消费等,这些功能和手机的开机关机功能属于两个不同的概念,依据接口隔离原则(ISP,Interface Segregation Principle)应该把它们分别定义在代表这两个概念的抽象表示(接口或者抽象类)中。这时“信用卡”这个抽象概念的定义可能是这两种情况:
interface Creditcard {
void e_wallet();
void consume();
}
或
abstract class Creditcard {
abstract void a_ wallet();
abstract void consume();
}
这时手机和银行卡这两个概念的定义方式就有了四种可能的组合,例如以下表:
|
手机 |
信用卡 |
方案A |
定义为抽象类 |
定义为抽象类 |
方案B |
定义为抽象类 |
定义为接口 |
方案C |
定义为接口 |
定义为抽象类 |
方案D |
定义为接口 |
定义为接口 |
方案A(两个概念都定义为抽象类)能够立即排除,由于Java不支持多继承。实现类这无法同一时候继承这两个抽象类。在这里能够看到抽象类在通过概念的组合来扩展功能的时候是
不方便的。
其它的方式眼下从语法上都是可行的,但谁更合理是值得考究的。
先研究一下方案C(“手机”定义为接口;“信用卡”定义为抽象类),这时实现这两个概念的类和这两个概念之间的关系就是,“like”手机,“is”信用卡,也就是说实现手机接口、继承信用卡抽象类的子类具有手机的功能,可是本质上是信用卡。显然这和我们对问题领域的理解不符。由于带信用卡功能的“手机”在概念本质上是手机。同一时候具有信用卡的功能能够像信用卡一样使用)。
所以这个方法是不合理的。
除非我们是在为信用卡的制造商写软件。他们希望增加手机的功能。并且,假设手机再扩展功能。如电子地图。导航器等等,把这样的扩展的功能概念像信用卡”一样定义为抽象类的话,由于Java的单继承机制。这是无法实现的。
这道理和方案A一样,把用来扩展功能的概念定义为抽象类并不合适。
方案B(“手机”定义为抽象类。“信用卡”定义为接口)应该是眼下最合理的设计了,这时反映出的概念是is手机,like“信用卡”。
假设有扩展功能的话。能够再定义成接口,成为“is手机。like信用卡like电子地图”。从而正确的反应我们面对的问题域。
那方案D两者都定义为接口,是不是就不行呢?相对方案C来说。方案D的设计没有反映出手机”是问题领域的本质的主体。使人有究竟我们在搞手机还是信用卡还是别的什么东西?”这个疑问。这个缺点是不容置疑的。但从还有一方面来说:“手机”这个概念定义成接口。在软件规模扩大的前提下,或许为以后其它的组件的使用提供了方便。比方说,如果厂家又有一个遥控器”的概念要我们设计。要把手机的功能设计进去,这时时候手机”如果是接口就方便了,implements他即可。
所以说。方案D是牺牲了概念的清晰性。得到了扩展
假设预见到问题领域以后没有太大变化,方案B是最好的。
方案D在眼下是不合适的,但在以后的扩展中或许非常方便。
这里得到的结论就是:假设仅仅是在定义一组行为框架的话。抽象类合适用来定义问题领域中的本质的抽象概念,接口合适用来定义扩展功能的抽象概念。
在刚刚这个样例中“手机”。“信用卡”不过一组抽象方法。也就是概念中含有的不过行为框架没有实现,这时候定义成抽象类或接口都有自己的道理。假设概念中己经含有了实现,这时候就把该概念定义成抽象类了。
比方一个“A系列打印机”的抽象类,由他定义不同类型的打印机,那一系列的打印机打印页头。页脚的方案都是一样的。但打印页面主体比較复杂,各种详细型号的打印机的各有它们不同的打印方法。这时能够这么设计:
方案一:依照打印机应该打印完整页面的自然逻辑, PrintBody()抽象方法是打印机这个概念的一部分。设计为抽象类:
abstract class A_SeriesPrinter{
abstract protected void PrintBody();
public void OutReport() {
PrintHeader();
PrintBody();
PrintFooter();
}
protected void Draw(String str) { /*实现的代码*/ }
protected void PrintHeader() { Draw("Head");/*实现的代码*/ }
protected void PrintFooter() { Draw("Footer");/*实现的代码*/ }
}
}
继承抽象类的代码:
classXXPrinter extends A_ SeriesPrinter {
protected void PrintBody() { /*实现的代码‘/ }
}
classYYPrinter extends A_SeriesPrinter {
protected void PrintBody() { /*实现的代码‘/ }
}
运用的代码:
XXPrinter xx = new XXPrinter();
xx.OutReport();
YYPrinter yy = new YYPrinter();
yy.OutReport();
显然这个方法是简单而清楚的。
方案二:为了扩展性,硬把PrintBody()抽象方法取出来成为一个接口IBody,代码例如以下:
abstractclass A_SeriesPrinter { //思考一下,还用abstract么?
protected void Draw( String str) { /*实现的代码*/ }
protected void PrintHeader() { Draw("Head");/*实现的代码*/ }
protected void PrintFooter() { Draw("Footer");/*实现的代码*/ }
}
interface IBody{
void PrintBody();//多了一个IBody接口的概念
}
在这里先解决一个问题,假设Printer去掉了PrintBody()抽象方法,都是实现了的方法,是不是就应该把它定义为普通的类呢?
答案是否定的。设计一个抽象概念为抽象类的意义,不是由于它含有抽象方法。而主要由于是他表示的概念不应该被实例化,即使它里头的方法所有是实现了的,仅仅是想让子类继承的代码。在上面这个样例中,“A系列打印机”这个概念。是不应该有实例的。有实例的应该是详细型号的打印机。所以,即便是所有是实现了的方法,方案二中的A_SeriesPrinter还是定义成抽象类更好。
继续看继承类并实现接口的代码:
classXXPrinter extends A_SeriesPrinterimplements IBody {
public void PrintBody() { ;/*实现的代码*/ }
public void OutReport() { // OutReport()被迫移到了实现类
PrintHeader();
PrintBody();
PrintFooter();
}
}
classYYPrinter extends A_SeriesPrinterimplements Body {
public void PrintBody() { ;/*实现的代码*/}
public void OutReport(){ // OutReportQ被迫移到了实现类
PrintHeader();
PrintBody();
PrintFooter();
}
}
运用的代码:
XXPrinter xx = new XXPrinterQ;
xxDutReportQ;
YYPrinter yy = new YYPrinterQ;
yy.OutReportQ;
这样做会显得非常奇怪和复杂: class XXPrinter extends Printer implementsIBody?好像打印Body居然是打印机的附加功能?(这太让人难以理解了),还无端的多出了一个IBody接口的概念。
并且,OutReport()被迫移到了各个实现类,代码变长并且复杂了。所以这时抽象类是最好的选择。除非有业务要求须要把Body的打印从打印机分离出来。套到别的概念中去。这时才有考虑使它成为接口的可能。但再次提醒大家,代码会变得复杂。追溯问题出现的源头,是由于PrintBody()这个抽象方法和打印机这个概念结合的太紧密了,它本身就是打印机功能的必不可少的一部分。贪图接口语法上的灵活性,自目的追求扩展性开放性,而不顾对问题领域的理解而建模,仅仅要某一个概念(A_SeriesPrinter)中含有的行为框架(PrintBody())都分离出来搞成接口,就会有一系列的编码上和理解上的麻烦,反而添加了代码的复杂性。
然而,即使在使用抽象类的场合,也不要忽视通过接口定义行为模型的原则。假设依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为可以更有效地分离行为与实现。为代码的维护和改动带来方便。比方我扩展A_ SeriesPrinter类。在打印后加个日志信息,如viod outLog()方法,那么我就不应该把它定义成A_SeriesPrinter类的抽象方法了,而是日志接口的抽象方法。由于“日志”这概念不属于打印机的专有范畴。
这样以后其它模块用到关于日志的操作规范时可以方便地用到这个日志接口。
所以,关键在于是否能出色地结合业务要求对问题域进行理解分析。假设你没有做好这点,你就不能建立合理的模型。这时要不就是添加编码的复杂性,可理解性。要不就是代码难以随着业务扩展而维护和改动。
综上。
1) 假设仅仅是在定义一组行为框架的话(抽象类和接口都能够实现),抽象类合适用来定义
问题领域中的本质的抽象概念,接口合适用来定义扩展功能的抽象概念。
2) 当须要为一些相关的类提供公共的实现代码时。应该优先考虑用抽象类来实现,由于抽
象类中的非抽象方法能够被子类继承下来,使实现功能的代码更简单
3) 当注重代码的扩展型和可维护性时,应该优先考虑使用接口。原因有:①接口与实现它
的类之间能够不存在不论什么层次关系,接口能够实现毫不相关类的同样行为,比抽象类的使用更加方便灵活;②接口仅仅关心对象之间的交互的方法,而不关心对象所相应的详细类。接口是程序之间的一个协议。比抽象类的使用更安全、清晰。一般使用接口的情况很多其它[1]。
简单说的说“在仅仅是定义一组行为框架时,对于与问题域中的抽象概念的定义应该优先考虑採用抽象类。要为一些相关类提供公共的实现代码时,应该优先考虑採用抽象类;其他情况都应该优先考虑採用接口”。——zhouyong
补充:
问题域[[5]](Problemdomain):指提问的范围、问题之间的内在的关系和逻辑可能性空间。
在软件project中,问题域是指被开发系统的应用领域,即在客观世界中由该系统处理的业务范围。
以上是关于Java中接口和抽象类的比較的主要内容,如果未能解决你的问题,请参考以下文章