四、接口的高级用途
一个接口被经常用得到的地方是
Collection Framework
。这个框架定义了一组非常重要的接口,它们是由一组多个类实现的。通过仅仅研究主要的接口,你可以有效的掌握整个框架,因为特别的实现类一般不影响设计的整体。
��例如,
List
接口定义了一个有序的元素集合。可用地实现包括
ArrayList
和
LinkedList
,它们都实现了
List
接口。当你的程序需要处理
List
时,不必考虑它是
ArrayList
还是
LinkedList
,只要知道所选用的类的属性即可。这个属性就是接口。
��通过实现类的接口,并且在类设计时仅对外公布接口,你就有效的封装了类的定义,这样后台实现的变动将对系统其它部分的影响最小。
��以
ArrayList
和
LinkedList
为例。将
ArrayList
看作一个可增长的数组对象(指是存储对象,而不是原始数据)。当类实现了
List
的全部接口时,它的特性在特定条件下是可以优化的。
��例如,如果你的程序是要对对表中的数据进行频繁的随机访问,(例如,显示第
3
,
12
,
2
,和
25
项数据)
ArrayList
类提供对列表中每一个数据快速查询。快速查询是以在列表中间添加和删除数据的速度为代价的。如果后一种行为是你需要的,那么
LinkedList
类是一个好的选择。它提供快速的顺序访问、添加和删除,但是,代价是慢速随机查询。
��在处理
ArrayList
和
LinkedList
时,有两种方式创建对象:
��
List cityList = new ArrayList() ;
��
LinkedList peopleList = new LinkedList() ;
��两个代码段都是正确的,但是这两行代码之间存在的显著的差别。第一行表示我们要创建一个
ArrayList
,但是我们只需要把它作为一个
List
来访问。第二行正好相反。是
LinkedList
项目被创建了,
ArrayList
也一样。但是,声明部分说明它只能作为
LinkedList
来访问,这就数它的最大区别。
��理解接口真正变的重要是在这两行代码的用户确定
“
查询项目
”
比删除(或添加)项目更为重要时。
��
PeopleList
变量被声明为
LinkedList
类型。这不是它本身的问题,因为你研究的更深层次的内容,你将发现
peopleList
在任何地方都被作为
LinkedList
对象处理。在你对
peopleList
使用
LinkedList
特有的方法的同时,如果你想把它作为
ArrayList
来处理,将会出现问题。
��
List peopleList = new ArrayList() ;
��通过学习仅使用接口来处理任何对象,你将发现在设计完成之后修改实现,你需要修改的仅仅是声明部分,除此之外,没有任何代码改动。这就是接口的绝妙之处。因为类的最初声明是
LinkedList
,当类型变为
List
时意味着象
addFirst
或
addLast
这样的方法是无效的,因为新的
peopleList
的类型是
List
,它没有这些方法。
��这种基于接口设计的代码,就像
Collection Framework
所向大家承诺的那样,任何人编写的代码是以循环构造方式进行的,而无需知道使用的是哪个
Collection
。创建的类是被限制为提供接口的完全实现。除此之外,新代码将不能被编译。
��作为实例,下面的程序创建一组集合。每个集合提供一个系统定义的
Iterator
这样集合的每个元素可以被访问。这个
iterator
将被传递到帮助例程,在这里集合的独立元素将被打印。
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
public class Interfaces
{
public static void main(String args[])
{
Properties props = System.getProperties();
Set keySet = props.keySet();
dumpIterator(keySet.iterator());
List list = Arrays.asList(args);
dumpIterator(list.iterator());
}
private static void dumpIterator(Iterator itor)
{
//System.out.println(itor.getClass().getName());
while (itor.hasNext())
{
System.out.println(">> " + itor.next());
}
System.out.println("----");
}
}
类
Iterator
的类型是
unknown
,这正是接口的绝妙之处,而不是问题。真正的事实是
iterator
方法返回的是一个真实的
Iterator
对象。然而,
dumpIterator
通常提供接口的完全实现。
��如果你去掉
dumpIterator
中的
println
行的注释,你将发现真实的
iterator
类名,对
Properties
是
Hashtable.Enumerator
而
List
是
AbstractList.Itr
。这个事实不必知道,也不会对你的程序有任何帮助。真正重要的是
List
和
Properties
的
iterator
方法所返回的任何对象,必须实现
java.util.Iterator:hasNext, next
和
remove
方法。没有这三种方法中任何两种,
dumpIterator
方法将永远不能工作。
五、抽象类和接口的比较
从语法定义层面看
abstract class
和
interface
��在语法层面,
Java
语言对于
abstract class
和
interface
给出了不同的定义方式,下面以定义一个名为
Demo
的抽象类为例来说明这种不同。
��使用
abstract class
的方式定义
Demo
抽象类的方式如下:
abstract class Demo
{
abstract void method1();
abstract void method2();
…
}
��使用
interface
的方式定义
Demo
抽象类的方式如下:
��
interface Demo
{
void method1();
void method2();
…
}
��在
abstract class
方式中,
Demo
可以有自己的数据成员,也可以有非
abstarct
的成员方法,而在
interface
方式的实现中,
Demo
只能够有静态的不能被修改的数据成员(也就是必须是
static final
的,不过在
interface
中一般不定义数据成员)
,所有的成员方法都是
abstract
的。从某种意义上说,
interface
是一种特殊形式的
abstract class
。
��从编程的角度来看,
abstract class
和
interface
都可以用来实现
"design by contract"
的思想。但是在具体的使用上面还是有一些区别的。
��首先,
abstract class
在
Java
语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个
interface
。也许,这是
Java
语言的设计者在考虑
Java
对于多重继承的支持方面的一种折中考虑吧。
��其次,在
abstract class
的定义中,我们可以赋予方法的默认行为。但是在
interface
的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会增加一些复杂性,有时会造成很大的麻烦。
��在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过
abstract class
或者
interface
来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过
abstract class
来实现的,那么可能就只需要修改定义在
abstract class
中的默认行为就可以了。
��同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了
"one rule
,
one place"
原则,造成代码重复,同样不利于以后的维护。因此,在
abstract class
和
interface
间进行选择时要非常的小心。
从设计理念层面看
abstract class
和
interface
��上面主要从语法定义和编程的角度论述了
abstract class
和
interface
的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:
abstract class
和
interface
所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。
��前面已经提到过,
abstarct class
在
Java
语言中体现了一种继承关系,要想使得继承关系合理,必须父类和派生类在概念本质上应该是相同的。对于
interface
来说则不然,并不要求
interface
的实现者和
interface
定义在概念本质上是一致的,仅仅是实现了
interface
定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。
��考虑这样一个例子,假设在我们的问题领域中有一个关于
Door
的抽象概念,该
Door
具有执行两个动作
open
和
close
,此时我们可以通过
abstract class
或者
interface
来定义一个表示该抽象概念的类型,定义方式分别如下所示:
��使用
abstract class
方式定义
Door
:
abstract class Door
{
abstract void open();
abstract void close();
}
��使用
interface
方式定义
Door
:
interface Door
{
void open();
void close();
}
��其他具体的
Door
类型可以
extends
使用
abstract class
方式定义的
Door
或者
implements
使用
interface
方式定义的
Door
。看起来好像使用
abstract class
和
interface
没有大的区别。
��如果现在要求
Door
还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示
abstract class
和
interface
反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
��解决方案一:
��简单的在
Door
的定义中增加一个
alarm
方法,如下:
abstract class Door
{
abstract void open();
abstract void close();
abstract void alarm();
}
��或者
interface Door
{
void open();
void close();
void alarm();
}
��那么具有报警功能的
AlarmDoor
的定义方式如下:
class AlarmDoor extends 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
语言中表示一种继承关系。所以对于
Door
这个概念,我们应该使用
abstarct class
方式来定义。另外,
AlarmDoor
又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过
interface
方式定义。如下所示:
��
abstract class Door
{
abstract void open();
abstract void close();
}
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
的功能,那么上述的定义方式就要反过来了。
本文出自 “wnight88” 博客,转载请与作者联系!