posts - 134,  comments - 22,  trackbacks - 0

抽象类和接口

         在实际的项目中,整个项目的代码一般可以分为结构代码和逻辑的代码。就像建造房屋时,需要首先搭建整个房屋的结构,然后再细化房屋相关的其它的结构,也像制造汽车时,需要首先制作汽车的框架,然后才是安装配件以及美化等工作。程序项目的实现也遵循同样的道理。

         在项目设计时,一个基本的原则就是——“设计和实现相分离”。也就是说结构代码和逻辑代码的分离,就像设计汽车时只需要关注汽车的相关参数,而不必过于关心如何实现这些要求的制作。程序设计时也是首先设计项目的结构,而不用过多的关系每个逻辑的代码如何进行实现。

         前面介绍的流程控制知识,主要解决的是逻辑的代码的编写,而类和对象的知识,则主要解决结构代码的编写。那么还有一个主要的问题:如何设计结构代码呢?这就需要使用下面介绍的抽象类和接口的知识了。

抽象类

         抽象类(Abstract Class)是指使用abstract关键字修饰的类,也就是在声明一个类时加入了abstract关键字。抽象类是一种特殊的类,其它未使用abstract关键字修饰的类一般称作实体类。例如:

                   public abstract class A{

         public A(){}

}

         抽象方法(Abstract Method)是指使用abstract关键字修饰的方法。抽象方法是一种特殊的方法,其它未使用abstract关键字修饰的方法一般称作实体方法。

                   public abstract void test();

         抽象类和实体类相比,主要有以下两点不同:

l  抽象类不能使用自身的构造方法创建对象(语法不允许)

例如下面的语法是错误的:

         A a = new A();

但是抽象类可以声明对象,例如下面的代码是正确的:

         A a;

         A a1,a2;

只是声明出的对象默认都是null的,无法调用其内部的非静态属性和非静态方法。

说明:抽象类可以使用子类的构造方法创建对象。

l  抽象类内部可以包含任意个(0个、1个或多个)抽象方法

抽象类内部可以包含抽象方法,也可以不包含抽象方法,对于包含的个数没有限制。而实体类内部不能包含抽象方法。

         在抽象类内部,可以和实体类一样,包含构造方法、属性和实体方法,这点和一般的类一样。

抽象方法和实体方法相比,主要有以下几点不同:

l  抽象方法没有方法体

也就是说在声明抽象方法时,不能书写方法体的{},而只能以分号结束方法。下面是实体方法和抽象方法声明的比较:

         抽象方法声明:

                   public abstract void test(int a);

         实体方法声明:

                   public  void test(int a){

                            方法体

                   }

l  抽象方法所在的类必须为抽象类

也就是说,如果抽象方法声明在一个类内部,则该类必须为抽象类。(说明:抽象方法也可以出现在接口内部,这个将在后续进行介绍)

这样,在继承时,如果继承的类是抽象类,而该抽象类中还包含抽象方法时,则该子类必须声明成抽象类,否则将出现语法错误。如果子类需要做成实体类的话,则必须覆盖继承的所有抽象方法。这个是抽象类最核心的语法功能——强制子类覆盖某些方法。

         介绍了这么多抽象类和抽象方法的知识以后,那么抽象类有什么用途呢?

         抽象类的用途主要有两个:

l  严禁直接创建该类的对象

如果一个类内部包含的所有方法都是static方法,那么为了避免其它程序员误用,则可以将该类声明为abstract,这样其它程序员只能使用类名.方法名调用对应方法,而不能使用对象名.方法名进行调用。这样的类例如API中的Math

说明:配合final关键字使用,将必须该类被继承,这样将获得更加完美的效果。

l  强制子类覆盖抽象方法

这样可以使所有的子类在方法声明上保持一致,在逻辑上也必须将方法的功能保持一致。例如游戏中设计类时,设计了怪物类以及相关的子类,每个怪物类都有移动方法,但是每种怪物的移动规则又不相同,这样通过使每个怪物类的移动方法的声明保持一致,方便调用。可以参看前面多态部分的介绍获得更多的关于调用统一知识。

这是抽象类最主要的用途。就像现实社会中,各种银行网点保持统一的装修风格,各种快餐店(肯德基、麦当劳等)保持统一的装修甚至风味,这样便于生活中的识别。通过让存在继承关系的类中功能一样(但是内部实现规则不同)的方法声明成一样的,方便多态的使用。

那么什么时候在设计时使用抽象类呢?这个问题参看一下抽象类的用途自然就知道了。关于抽象类的知识先介绍这么多,下面介绍接口的知识,最终将对抽象类和接口进行一下比较。


接口

         接口(Interface)是一种复合数据类型。

         至此,Java语言的所有数据类型介绍完了,下面进行一个简单的总结。Java语言的数据类型分为两大类:基本数据类型和复合数据类型,其中基本数据类型有8种,复合数据类型包括数组、类和接口,由于开发过程中可以根据需要声明新的复合数据类型,所以复合数据类型的数量有无限个。

         接口的概念,现实中使用的也很多,例如大家经常使用的U盘,则需要和计算机上的USB接口匹配使用,而且USB设备中除了U盘以外还有很多,例如USB风扇、USB数据线、USB鼠标、USB键盘等,他们都使用计算机上统一的USB接口,这样设备的通用性很强。简化了计算机接口的设计,使计算机不需要具备鼠标接口、键盘接口等专用的结构。

         广义上来说,两个人说不同的方言,互相之间无法听懂另一方表达的意义,我们也可以称之为双方使用的接口不统一,CPU无法和主板匹配,我们也可以称之为接口不统一,例如AMDIntelCPU采用不同的针脚结构,甚至同一厂商不同型号的CPU针脚结构也不统一,这样很不方便设备之间的匹配,使用专业的技术术语叫作兼容性差。

         那么什么是接口呢?其实接口就是一套规范。

         例如USB接口,分为两套规范:公接口和母接口。例如U盘、USB鼠标上的USB接口为公接口,而电脑上的USB接口为母接口。规范中只规定公接口有4个通道,那些用来传输数据、那些用来进行供电,母接口规范只规定也有4个通道,那些用来传输数据,那些用来进行供电,电压是多少电流多大等。所有的这些规范都只规定了必须实现那些功能,但是却没有规定如何进行实现。

         这种只规定实现什么功能,而不限制如何进行实现的结构,在程序设计领域中称作“设计和实现相分离”,其中规定实现的功能属于设计部分,而如何实现功能则是实现部分。这样进行程序项目制作,可以让一部分人专门进行项目设计,而由另一部分人进行项目实现。这点,很类似汽车的制造,由设计人员设计汽车,由制造人员进行制造。

         这种“设计和实现相分离”的结构将极大的简化程序项目的设计和管理,使得整个项目的分工更加细致,也就是使程序设计完全独立出来,而在设计完成以后再进行代码编写。

         接口就是一个纯粹用来设计的数据类型,在接口这种数据类型中,只能书写两类声明的结构:

l  常量数据

所有的常量数据都是public static的。如果声明时不书写则系统将自动添加这两个修饰符。

l  抽象方法

接口中的所有方法都只在逻辑上规定该方法的作用,而不能书写方法体。所有接口中的方法都是public abstract的,如果声明时不书写则系统将自动添加这两个修饰符。

         其中接口中的数据是常数,以后不能改变,而方法只是规定要做什么,而不去规定如何进行实现。这样接口就很方便设计人员进行设计,而不必过多的关系对应的方法如何在逻辑上进行实现。

         接口声明的语法格式如下:

                   访问控制符 interface 接口名 [extends 父接口名1,父接口名2……]{

                            常量声明

                            方法声明

                   }

         和类的声明一样,访问控制符只能使用public和默认的。声明时使用interface关键字声明接口,接口可以继承其它的接口,使用extends关键字进行继承,多个接口名之间使用逗号进行分隔。和类的集成一样,子接口继承父接口中所有的常量数据和方法,子接口的对象也是父接口的对象。

         注意:和抽象类一样,接口只能声明对象,而不能创建对象。

         接口声明的示例代码如下,例如声明一个USB接口来代表实际使用中的USB结构:

                   public interface USB{

                            /**电压*/

                            public static final int V = 5;

                            /**读取数据*/

                            public abstract byte[] readData();

                            /**写入数据*/

                            public abstract void writeData(byte[] data);

                   }

         该接口中规定电压常量为5V,声明了两个方法,要求实现USB时必须实现这样两个方法,至于如何实现这里不做规定。这样这个数据类型就只是设计上的说明,而不牵扯具体的实现,这样在项目中使用时则比较通用。

         从这点来看,接口类似于现实中使用的各个国家标准,标准中只规定该类型最终需要达到的标准,而不规定如何实现,各个厂商可以根据自己的产品工艺实现该要求即可。

         在实际的项目中,设计接口需要对于项目的整体有比较深刻的了解和认识,这样才可以设计出需要的接口结构,关于接口的设计这里不作太深入的论述。如果需要更深刻的了解设计的结构,可以参阅OReilly的《Designing.Interfaces》一书。

         接口设计完成以后,还需要再项目中实现接口规范中对应的要求,一般声明对应的类来实现接口,实现接口的语法为:

                   访问控制符 [修饰符] class 类名 [extends 父类名] implements 父接口名1,父接口名2……

         实现接口的语法位于类声明中,位于继承声明的后面,使用implements关键字代表实现,后续是需要实现的接口的名称,一个类可以实现任意多个接口。

         实现接口和继承类很类似,声明的类称作接口的子类,接口为该类的父接口,子类中继承父接口中所有的数据和方法,因为接口中所有的方法都是抽象方法,所以如果子类中不实现(覆盖)父接口中的方法,则该类必须声明为抽象类。

         例如计算机实现了USB接口,则示例代码如下:

                   public class Computer implements USB{

                            /**内存容量*/

                            int memorySize;

                           

                            public abstract byte[] readData(){

                                     //读数据的逻辑

                            }

 

                            public abstract void writeData(byte[] data){

                                     //写数据的逻辑

                            }

                   }

         这里,Computer类实现了前面的USB接口,在Computer类内部可以书写和Computer类相关的属性、方法和构造方法,这里对于实现接口没有影响,而因为实现了USB接口,则必须覆盖USB接口中的readDatawriteData抽象方法,至于方法内部的代码,则根据逻辑的需要进行实现,从而实现接口中要求实现的功能。

         类似的,也可以使一个数码相机实现USB接口,则实现的示例代码为:

                   public class DigitalCamera implements USB{

                            /**厂商名称*/

                            String vendorName;

                           

                            public abstract byte[] readData(){

                                     //读数据的逻辑

                            }

 

                            public abstract void writeData(byte[] data){

                                     //写数据的逻辑

                            }

                   }

         在该类中,也可以根据该类的需要实现USB接口中规定的功能,至于如何实现则很可能和Computer类不同。

         这样,虽然Computer类和DigitalCamera类自身原来的功能不同,但是都可以通过实现USB接口而实现同样的功能,这样单纯的从是否支持USB功能来看,这两个类的实现是一样的。按照面向对象的术语来说,这被称作屏蔽了类的类型之间的不同,保证了程序的通用性。

         由于实现接口时,不限制实现的接口的数量,则任何一个类都可以实现任意多个接口,这样就使类的通用性获得了极大的增强,很方便类的对象之间的匹配。就像现实中USB接口规范方便了多种不同设备之间的互联一样。

         在语法上,实现了接口的对象可以使用子类的构造方法进行创建,这样又很适合多态的结构,可以说接口的出现,使多态的特性更容易的进行实现。

         在实际项目中,通过使用一定的接口,使得很多类的对象在实现某种类型的功能时,方法的声明是统一的,便于程序的调用和管理,利于程序项目的扩展。所以在现在的面向对象编程领域中,存在着另外的一个方向——面向接口的编程,其实很多Java的技术都是这样进行实现的,例如JDBC部分。

         由于抽象类和接口的功能比较类似,后续将对于抽象类和接口进行一系列的比较,方便项目设计时的取舍。




抽象类和接口的比较

         抽象类和接口都是进行面向对象设计时专用的设计结构,在实际进行项目设计时,经常需要考虑的问题就是——“使用抽象类还是接口”?下面通过对于抽象类和接口进行简单的比较,熟悉两者之间的区别和联系,从而在实际设计时使用恰当的结构。

1.         什么时候使用抽象类或接口?

当设计中为了规范类中方法声明的结构(即类的行为)时,使用抽象类或接口。也就是强制子类对外部提供统一的方法声明时,使用抽象类或接口。

2.         抽象类和接口的区别(不同点)

a)抽象类是类,而接口是接口。

因为抽象类是一个类,所以类内部可以包含的内容(构造方法、方法和属性等)在抽象类内部都可以存在,当然抽象类也受到类的单重继承的限制。而接口是接口类型,所以接口内部只能包含常量属性和抽象方法,但是一个类可以实现多个接口,所以接口不受类的单重继承的限制。

b)抽象类内部可以包含实体方法,而接口不能

         抽象类是一个类,所以在抽象类内部既可以包含抽象方法也可以包含实体方法,而接口内部的每个方法都必须是抽象方法。

c)抽象类可以继承类,而接口不能

         抽象类是一个类,所以在设计时可以使抽象类继承其它的类,在已有类的基础上进行设计,但是接口不能继承类。

3.         抽象类和接口的联系(相同点)

a)抽象类和接口都可以声明对象,但是都只能使用子类的构造方法进行创建。

b)抽象类和接口内部都可以包含抽象方法。

         按照Java语言的语法,子类在继承抽象类或实现接口时,都必须覆盖这些抽象方法,否则必须声明为抽象类。

c)抽象类和接口都可以代表一种类型,从而可以统一子类对象的类型,获得良好的可扩展性。

4.         什么时候使用抽象类?

当满足以下的条件时,最好使用抽象类进行设计:

a)子类不继承其它父类

b)子类中存在完全相同的功能实现的方法

c)子类中存在相同的属性

d)设计出的结构需要继承其它类

当需要满足d条件时,只能使用抽象类,否则也可以考虑使用接口实现。

5.         什么时候使用接口?

当满足以下的条件时,最好使用接口进行设计:

a)子类已经继承了其它父类

b)子类中不存在完全相同的功能实现方法

c)子类中不存在相同的属性

d)设计出的结构不需要继承其它类

当需要满足a条件时,只能使用接口,否则也可以考虑使用抽象类实现。

6.         抽象类和接口的其它用途

a)禁止创建该类的对象时,可以把该类声明为抽象类。

b)当需要存储大量的常量数据,而这些常量数据将会在项目中的多个类之间使用时,可以使用接口。

c)当需要统一具有某种功能的类的对象时,可以使用接口。例如Serializable接口。

         当然,只有经过大量的系统设计训练以后,才可以更加深刻的理解抽象类和接口的区别和联系,从而更加自如的进行选择。

         另外,需要说明的是,不是每个项目中都必须使用抽象类或接口的。





posted on 2009-07-03 09:43 TRE-China R&D 阅读(386) 评论(0)  编辑 收藏 引用 所属分类: JAVA技术资源
只有注册用户登录后才能发表评论。