一、理解抽象类
本文的抽象类并不是从abstract class翻译来的,它表示的是一个抽象体,而abstract class 是Java语言中用于定义抽象体的一种方法。
在面向对象的概念中,所有的对象都是通过类来描述的,但反过来不成立:不是所有的类都是用来描绘对象的。如果一个类中没有包括足够的信息来描绘一个具体的对象,那么这样的类就是抽象类。抽象类用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是一系列看上去不同、但本质上相同的具体概念的抽象。
比如:我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行 为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。
1.相同点:
①抽象类与接口都包含未实现的方法;
②抽象类与接口都不能实例化;
③派生类都必须实现未实现的方法。
2.不同点:
首先,只有方法签名没有实现的函数叫做抽象方法。
①抽象类中出了包含抽象方法,还可以有实现的方法;接口中只有抽象方法。
②抽象类中可以有自己的数据成员,可以是非公共属性的;接口中只能有常量,static final,只读不能更改,具有公共属性。
③抽象类在Java语言上表示的是一种继承关系,可以从另一个类或者一个/多个接口派生;接口不能从另外一个类派生但可以实现其他一个/多个接口。
抽象类:
abstract class A{
private int x;
public abstract void doSomething();
public void test(){
System.out.println("test");
}
}
class B extends A{
public void doSomething(){
System.out.println("dosomething");//抽象方法的实现
}
}
接口:
interface A{
public static final int x=1;
public abstract void doSomething();
public abstract void test();
}
class B implements A{
@Override
public void doSomething() {
// TODO Auto-generated method stub
System.out.println("dosomething");
}
@Override
public void test() {
// TODO Auto-generated method stub
System.out.println("test");
}
}
三、abstract class 与 interface 的选择
很多时候我们在进行抽象类定义时对于abstract class 和interface的选择显得比较随意,上面只是从语法定义和编程的角度论述了两者的区别,是比较低层次的、非本质的。下面将从设计理念层看两者的区别,分析理解二者的概念本质所在。
前面提到过,abstract class在Java语言中体现了一种继承关系,要让继承关系合理,父类和派生类之间必须存在is a 的关系,即父类和派生类在概念本质上相同的。对于interface来说则不然,并不要求interface的实现者和interface本身在概念本质上是一致的,仅仅是实现了interface定义的契约而已。下面通过一个例子来理解:
假设我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class 或者 interface来定义一个表示该抽象概念的类型,定义方式分别如下:
//abstract class 方式定义:
abstract class Door {
abstract public void open();
abstract public void close();
}
//interface方式定义:
interface Door{
abstract public void open();
abstract public void close();
}
其他具体的Door类型可以extends使用abstract class方式定义或者implements使用interface方式定义。看起来好像没什么区别。
如果现在要求Door还要具有报警的功能,我们该如何设计针对该例子的类结构呢?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
【解决方案一】:
//简单地在Door定义中增加一个alarm的方法
abstract class Door{
abstract void open();
abstract void close();
abstract void alarm();
}
或者
//简单地在Door定义中增加一个alarm的方法:
interface Door{
abstract public void open();
abstract public void close();
abstract public void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
<pre name="code" class="java">class AlarmDoor extents Door{
void open(){
//...
}
void close(){
//...
}
void alarm(){
//...
}
}
或者
class AlarmDoor implements Door{
void open(){
//...
}
void close(){
//...
}
void alarm(){
//...
}
}
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。
【解决方案二】:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现 AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用 interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同 时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定 义。如下所示:
abstract class Door{
abstract void open();
abstract void close();
abstract void alarm();
}
interface Alarm{
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open(){
//...
}
void close(){
//...
}
void alarm(){
//...
}
}
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计 意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有 Door的功能,那么上述的定义方式就要反过来了。
【 总结 】:
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
看到论坛里有一个很有意思的回答:
为什么电脑主板上还要有内存插槽,显卡插槽?多浪费机箱空间啊?
直接用电烙铁把显卡和内存的引脚一根一根焊到主板上不就得了。
如果你焊错位置了,说明你焊工不好。
每次拆装的的时候再继续用电烙铁。
接口编程的直接好处就是:实现多态,同一个接口,却可以调用不同的底层实现。