对着月亮唱歌的幸福生活

即便是莲藕内心真空之所,也有根根柔丝穿过。
  博客园  :: 首页  :: 联系 :: 管理

Object-Oriented:抽象类(Abstract Class)

Posted on 2008-07-22 14:31  对月而歌  阅读(550)  评论(0)    收藏  举报

 

《Beginning c# Objects》读书笔记系列

1.: 继承(Inheritance)

2.: 多态(Polymorphism)

3.: 抽象类(Abstract Class)

4.: 接口(Interface)

5.:静态特征(Static Feature)

 

    把俩个或更多个类的共享特征----ttribute和行为----集中到一个公共的基类,是非常有用的,这种手法称做概括.我们对Student类饿Professor类进行概括,创建了Person类,然后从前面俩个类中移除了所有公共attribute、方法和property,把它们放到基类中。这样,派生类Student和Professor都变得简单,减少了一大堆可能使得维护SRS(学生选课系统)度麻烦的冗余。

    这个例子展示的是对已有的类进行概括的情形;现在来看看另一种可能。比如说,一开始我们就预见到SRS中需要多种不同的Course对象;讲座型课程,实验型课程、自学型课程等等。于是我们希望能跨出正确的第一步,设计一个万能的Course基类、满足未来分化的需要。

   我们可能会预见,对于所有的课程种类,都要共享几个公共attribute:

      *string courseName;

      *string courseNumber;

      *int creditValue;//[ 'kredit ] 学分

      *CollectionType enrolledStudents;//群集 已经登记的学生

      *Professor instructor;   //Professor类型的  教师

还有一些公共行为:

      *EstablishCourseSchedule   //建立课程时间表

      *EnrollStudent   //登记学生

      *AssignInstructor  //分配教师

    其中一些行为可能已经作够通用,我们可以直接在Course类中编写其细节,让 Course类的派生类之需要直接继承他们,不用进行覆载,例如

public class Course
{
 
string courseName;
 
string courseNumber;
 
int creditValue;
 Professor instructor;
 
//伪代码
 Collection enrolledStudents;
 
//提供了Property,细节从略 
 Public bool EnrollStudent(Student s){
  
//伪代码
  if (we haven't exceeded the maximum allowde enroolment yet)
  enrolledStudents.Add(s); //登记学生
 }
 
public void AssignInsturctor(Professor p){
  Instructor 
= p;
 }  
}

    然而,另外一些行为却可能与特定派生类型结合的太紧密,必须先创建更通用版本。例如,如何制定面授时间计划的业务规则,在不同课程中就有不同体现。

      *讲座课程只需没周面授一次,每次3小时。

      *实验型课程可能需要每周面授俩次,每次2小时。

      *自学型课程可能要由学生和教授协商决定面时间。

    所以,看起来为Course类的EstablishCourseSchedule方法编写通用版本代码石斛就是白费时间,因为它不能符合所有情况的需要;三种课程类型都的覆载它,从而符合自身需要。

    那么,我们能否干脆忽略EstablishCourseSchedule的方法呢?是否可以把它当作Course类的派生类只能感的新特性来处理呢?决定因素之一,是我们是否计划要在应用程序中实体化一个“普通的”Course对象。

      *如果要这样做,则Course类应该需要一个自己的EstablishCourseSchedule方法。

      *不过,即便没有直接试题话Course类的打算,如果希望使用EstablishCourseSchedule方法的多态行为,也有在Course类这一级别定义该方法的必要。

    假定我们步会实体化普通的Course对象,但希望利用多态技术。这样我们就会面临一个俩难的选择!我们需要为Course类的每个派生类分别定义不同版本的EstablishCourseSchedule方法,但又不想为父类编写无用的代码。那么,又怎么能够让派生类了解这个行为的需求,而且,更重要的是,强迫派生类实现它呢?

    类似C#这样的OO语言使用抽象类(abstract class)概念来救命了。抽象类用来列举一个类所需要的行为,但不明确提供每个行为的具体实现方法。为抽象类编写代码,和为非抽象类(concrete class,也称具体类)编写代码,方法是一样的,只有一处不同:对于那些我们 不能(或不愿)给出普遍实现行为----如前例的EstablishCourseSchedule方法----可以仅给出方法头,不用编写相应的方法体。我们把“无方法体”的,或仅有方法头的方法称做抽象方法(abstract method)。

    回到Course类的定义,添加一个抽象方法

 

public class Course
{
 
string courseName;
 
string courseNumber;
 
int creditValue;
 Professor instructor;
 
//伪代码
 Collection enrolledStudents;
 
//提供了Property,细节从略 
 Public bool EnrollStudent(Student s){
  
//伪代码
  if (we haven't exceeded the maximum allowde enroolment yet)
  enrolledStudents.Add(s); //登记学生
 }

 
public void AssignInsturctor(Professor p){
  Instructor 
= p;
 }

  
//注意 abstract关键字的用法,和末尾的分号,抽象方法
 public abstract void EstablishcourseSchedule(string startDate,string endDate);  
}

    通过在方法头中增加abstract关键字,EstablishCourseSchedul被生命抽象方法。注意,抽象方法的头部没有闭合的参数列表括号,而是直接跟了一个分号(;)----也就是说,它没有包括方法执行逻辑的方法体。这样,方法就被标记为抽象方法,提醒编译器注意,我们不是忘记为方法编写代码了,而是故意为之。

  指定一个抽象方法之后,我们得以达到几个重要目标:

      *我们定义了一个服务,不同的类型都得执行它;

      *我们明确了一个意愿,要求每个对象通过定义方法头来执行这个服务。该方法头控制了我们希望方法执行时传入的消息格式。

      *此外,通过确保所有Course类的派生类都能识别与该方法签名相关的方法调用,我们还使多态变的容易

    不过,我们却没有苟泥于方法如何完成任务----即派生类的业务规则----的私有细节。大体上,我们指定恶劣一种课程类型对象应该做什么,而不限制它怎样做。Course类的派生类型----LectureCourse(讲课)、LabCourse(实验课)、和independentStudyCourse----可以自由地使用具体版本代码覆载抽象方法,发应特定派生类型的业务规则。

    只要一个类包括一个或多个抽象方法,该类就必须抽象类,定义抽象类的手段是在类声明中加上abstrace关键字;

    public abstract class Course
      {// 细节从略

      }

    注意,抽象类中的方法并不一定都司抽象方法;抽象类也可容纳有方法体的方法,或称具体方法(concrete method).

抽象类和实体化

    对于抽象类有一个限制:它们不能被实体化。也就是说,如果在SRS中把Course类定义为抽象类,则我们就无法实体化一个普通Course对象,推而广之,如果能创建Course类的对象,则它将可望懂得如何响应常见课程计划的消息,因为Course类声名了EstablishCourseSchedule行为的一个方法头。但是,因为方法没有实际代码,Course对象就不会懂如何响应这个消息。

    编译器会组织我们实例化一个抽象类;如果试图编译下列代码片段:

    Course c = new Course();//不可能!编译器将产生错误信息。

    //细节从略。

    c.EstablishCourseSchedule('01/10/2001','05/15/2001');//没有定义行为!

    我们将得到以下错误信息:

    error CS0144:cannot create an instance of the abstract class ro interface'course'

    虽然不能实例化一个实例化一个抽象类,但却可以生命到一个抽象类的引用变量:

Course x;//这样没问题

    如果不能实体化Course类的一个对象,为什么还要声明Course类的引用变量呢?答案与多态有关,以后再说,能够为抽象类型定义应用变量的重要性.

覆载的抽象方法

    当从一个抽象基类派生一个类时,派生类将继承基类的多有特性,包括其抽象方法头。派生类可以通过override关键在字,用具体版本替换继承下来的抽象方法,如下列代码所示:

//抽象基类.
public abstract class Course
{
 
private string coureseName;
//等等
//其他细节从略
 public abstract void EstablishcourseSchedule(string startDate,string endDate);
 
string endDate);
}
//从抽象类派生第一个类
public class LectureCourse : Course
{
//细节从略.
//用具体方法替代抽象方法.
public override void EstablishCourseSchedule(string starDate,string endDate){
//为lectureCourse类指定业务规则
//细节从略
 }
}

    在用来覆载基类中生命的一个虚方法时,override关键字的用法和我们在前面看到的一样。注意,在前例中,我们把abstract关键字从LectureCourse派生类的覆载方法EstablishCourseSchedule中去掉了,因为该方法不再是抽象方法,我们为它提供了集体的方法体。

    如果派生类没有为从抽象基类继承下来的所有抽象方法具体实现,则派生类自动被当作抽象类处理。在这中情况下也不能被实体化。所以,在派生层级的某个地方,从一个抽象类派生出的某个类,如果希望“冲破被抽象的诅咒”----实体化该派生类型的对象,必须实现其所有祖先的所有抽象方法。

冲破抽象的诅咒

    来看一个更详细的例子,我们有意把Course类设计为一个抽象类,当作SRS中所有不同课程种类的公共模版,然后我们决定从中派生出LectureCourse类、LabCourse类、和IndependentStudyCourse类。在下面的代码片段中,展示了Course类的三种派生类;其中,只有俩个----LectureCourse和LabCourse提供了抽象方法EstablishCourseSchedule的具体实现,所以,第三个派生类----IndepentStudyCourse----仍然是抽象类,不能被实体化。

public class LectureCourse:Course
{
 
//所有的attribute都继承自Course类;
 
//没有添加新的attribute
 public override void EstablishCourseSchedule(string startDate,string endDate){
 
//需要添加关于讲座类型课程的安排面授时间逻辑,细节从略
  }
}
public class LabCourse:Course
{
 
//所有的attribute都继承自Course类;
 
//没有添加新的attribute.
 public override void EstablishCourseSchedule(string startDate,string endDate){
 
//需要添加关于讲座类型课程的安排面授时间逻辑,细节从略
  }
}
public class IndependentStudyCourse:Courese
{
 
//所有的attribute都继承自Course类;
 
//没有添加新的attribute
 
//故意不在这个派生类里面实现EstablishCourseScheduyle方法
}

    如果试图编译以上代码,C#编译器会要求我们把IndependentStudyCourse类标记为抽象类,我们将得到以下编译错误:

    error CS0534:' IndependentStudyCourse'does not implement in herited abstrace member

    除非我们把IndependentStudyCourse类的生命修改为abstract:

    public abstract class IndependentStudyCourse:Course

   {细节从略}

    我们看到,抽象方式是如何被用来强制要求实现代码的!在基类中证明一个抽象方法,将强制所有派生类为继承下来的抽象方法提供与本类型相关实现,否则,派生类本身本身就无法被实体化。

 

注意  让IndependentStudyCourse继续做抽象类并没有犯错;错误之处在与试图将他实体化。我们可能会从IndependentStudyCourse类派生出“新一代”的类如----IndependentStudyGraduateCourse和IndependentStudyUnderGraduateCourse---让他们实现具体方法。在继承层级中拥有很多层的抽象类四可以接受的;我们只需要一个具体的末端/枝叶,用她来创建对象
     我们已经看到,通过提供对抽象类声明的所有抽象方法的非抽象实现,就能凑抽象类派生出新类,另一天路也行得通:派生类可以用一个抽象方法来覆载基类中的非抽象方法。