[J2SE 基础知识]2、抽象类和接口(下)

四、接口的高级用途

    一个接口被经常用得到的地方是 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” 博客,转载请与作者联系!

你可能感兴趣的:(接口,抽象类,面向对象,J2SE,休闲)