C#面向对象设计(OOP)之对象多态(Polymorphic)
作者:C/S框架网  发布日期:2011-07-14 21:20:53
C#面向对象设计(OOP)之对象多态(Polymorphic)

C#面向对象的多态

一、什么是多态

面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。

在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。

“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性:

● 编译时的多态性

编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

● 运行时的多态性

运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。

编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

二、实现多态

多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力。多态性允许对类的某个方法进行调用而无需考虑该方法所提供的特定实现。例如,可能有名为 Road 的类,它调用另一个类的 Drive 方法。这另一个类 Car 可能是 SportsCar 或 SmallCar,但二者都提供 Drive 方法。虽然 Drive 方法的实现因类的不同而异,但 Road 类仍可以调用它,并且它提供的结果可由 Road 类使用和解释。

可以用不同的方式实现组件中的多态性:

● 接口多态性。

● 继承多态性。

● 通过抽象类实现的多态性。

接口多态性

多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,但将这些成员的特定实现留给实现类去完成。

组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性和事件组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心功能。其他开发人员最常使用的组件功能自然是组件类本身的成员。然而,包含大量成员的组件使用起来可能比较困难。可以考虑将组件的某些功能分解出来,作为私下实现的单独接口。

根据接口来定义功能的另一个好处是,可以通过定义和实现附加接口增量地将功能添加到组件中。优点包括:

1.简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功能,同时不断插入其他的功能,并通过实际使用那些功能来确定合适的功能。

2.简化了兼容性的维护,因为组件的新版本可以在添加新接口的同时继续提供现有接口。客户端应用程序的后续版本可以利用这些接口的优点。

通过继承实现的多态性

多个类可以从单个基类“继承”。通过继承,类在基类所在的同一实现中接收基类的所有方法、属性和事件。这样,便可根据需要来实现附加成员,而且可以重写基成员以提供不同的实现。请注意,继承类也可以实现接口,这两种技术不是互斥的。

C# 通过继承提供多态性。对于小规模开发任务而言,这是一个功能强大的机制,但对于大规模系统,通常证明会存在问题。过分强调继承驱动的多态性一般会导致资源大规模地从编码转移到设计,这对于缩短总的开发时间没有任何帮助。

何时使用继承驱动的多态性呢?使用继承首先是为了向现有基类添加功能。若从经过完全调试的基类框架开始,则程序员的工作效率将大大提高,方法可以增量地添加到基类而不中断版本。当应用程序设计包含多个相关类,而对于某些通用函数,这些相关类必须共享同样的实现时,您也可能希望使用继承。重叠功能可以在基类中实现,应用程序中使用的类可以从该基类中派生。抽象类合并继承和实现的功能,这在需要二者之一的元素时可能很有用。

通过抽象类实现的多态性

抽象类同时提供继承和接口的元素。抽象类本身不能实例化,它必须被继承。该类的部分或全部成员可能未实现,该实现由继承类提供。已实现的成员仍可被重写,并且继承类仍可以实现附加接口或其他功能。

抽象类提供继承和接口实现的功能。抽象类不能示例化,必须在继承类中实现。它可以包含已实现的方法和属性,但也可以包含未实现的过程,这些未实现过程必须在继承类中实现。这使您得以在类的某些方法中提供不变级功能,同时为其他过程保持灵活性选项打开。抽象类的另一个好处是:当要求组件的新版本时,可根据需要将附加方法添加到基类,但接口必须保持不变。

何时使用抽象类呢?当需要一组相关组件来包含一组具有相同功能的方法,但同时要求在其他方法实现中具有灵活性时,可以使用抽象类。当预料可能出现版本问题时,抽象类也具有价值,因为基类比较灵活并易于被修改。

三、虚方法

当类中的方法声明前加上了virtual 修饰符,我们称之为虚方法,反之为非虚。使用了virtual 修饰符后,不允许再有static, abstract, 或override 修饰符。

四、接口多态性

多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,但将
这些成员的特定实现留给实现类去完成。

组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性和事件组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心功能。

其他开发人员最常使用的组件功能自然是组件类本身的成员。然而,包含大量成员的组件使用起来可能比较困难。可以考虑将组件的某些功能分解出来,作为私下实现的单独接口。

根据接口来定义功能的另一个好处是,可以通过定义和实现附加接口增量地将功能添加到组件中。优点包括:

● 简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功能,同时不断插入其他的功能,并通过实际使用那些功能来确定合适的功能。

● 简化了兼容性的维护,因为组件的新版本可以在添加新接口的同时继续提供现有接口。客户端应用程序的后续版本可以利用这些接口的优点(如果这样做有意义)。

五、继承多态性

多个类可以从单个基类“继承”。通过继承,类在基类所在的同一实现中接收基类的所有方法、属性和事件。这样,便可根据需要来实现附加成员,而且可以重写基成员以提供不同的实现。请注意,继承类也可以实现接口,这两种技术不是互斥的。

C# 通过继承提供多态性。对于小规模开发任务而言,这是一个功能强大的机制,但对于大规模系统,通常证明会存在问题。过分强调继承驱动的多态性一般会导致资源大规模地从编码转移到设计,这对于缩短总的开发时间没有任何帮助。


下面的实例演示接口多态性

定义测试用的类.IFood接口:食物接口,所有食物实现这个接口,用于测试接口多态性.

//食物接口
public interface IFood
{
   //食物名称
   string Name { get;set;}
}

//水果系列
public class Fruit : IFood
{
   protected string _Name = "";
   public string Name { get { return _Name; } set { _Name = value; } }
}

//香蕉
public class Banana : Fruit
{
   public Banana() { _Name = "香蕉"; }
}

//苹果
public class Apple : Fruit
{
   public Apple() { _Name = "苹果"; }
}

//西瓜
public class Watermelon : Fruit
{
   public Watermelon() { _Name = "西瓜"; }
}


食物订单

//食物订单
public class MyFoodOrder
{
   //我的食物订单返回一系列食物,可能放满一卡车也许是一火车,不关心究竟有多少.
   //究竟有那些食品种类我自己也不知道,除非去看采购订单。
   //在这种情况下创建订单要用上对象多态.比如下面定义的IList<IFood>泛型返回值.       
   public IList<IFood> GetFoods()
   {
      //IList接口引用创建List对象也是多态特性.注意:加入了三个种类的水果,如没有多态性能加入不同种类的水果吗?
      IList<IFood> list = new List<IFood>(); //这里也是多态特性
      list.Add(new Apple());//苹果
      list.Add(new Apple()); 
      list.Add(new Banana());//香蕉
      list.Add(new Watermelon());//西瓜
      return list;
   }
}


假设这里不使用多态特性, 那么你必须创建几十甚至几百个方法返回每个种类的食品,如下: 
//食物订单II
public class MyFoodOrderII
{
   public IList<Apple> GetApples()
   {
      IList<Apple> list = new List<Apple>();
      list.Add(new Apple());
      list.Add(new Apple());
      return list;
   }
   
   public IList<Banana> GetBananas()
   {
      IList<Banana> list = new List<Banana>();
      list.Add(new Banana());
      return list;
   }
   
   public IList<Watermelon> GetWatermelon()
   {
      IList<Watermelon> list = new List<Watermelon>();
      list.Add(new Watermelon());
      return list;
   }

   ..........其它更多种类.......

   ..........这样的代码郁闷么????.................
}


爱吃水果的小孩子
//爱吃水果的小孩子
public class PrettyBaby
{
   //这个小孩子喜欢吃水果,几乎喜欢吃任何水果. 
   //多态特性应用之2.参数为IFood接口类型,那么这小孩能吃所有实现IFood接口的水果
   public void Eat(IFood food)
   {
      Console.WriteLine("小孩子在吃:" + food.Name);
   }
}

假设不使用多态,这小孩子喜欢吃100种水果,你是不是要定义100个Eat(吃)方法????? 暴汗罗!

public class PrettyBabyII
{
   public void Eat(Apple food){ }
   public void Eat(Watermelon food) { }
   public void Eat(Banana food) { }
   
   public void Eat(xxx xxx) { }

   .........Eat 100 methods......
   ..........这样的代码郁闷么????.................
}


测试用例
//测试用例
public class Tester
{
   //测试订单
   public void TestOrder()
   {
      MyFoodOrder order = new MyFoodOrder();
      IList<IFood> foods = order.GetFoods(); //获取订单内的食品列表
      this.PrintFoods(foods);
   }
   
   //测试吃
   public void TestEat()
   {
      PrettyBaby baby = new PrettyBaby();//创建一个小孩子
      IFood apple = BuyApple(); //小孩子去买一个苹果
      baby.Eat(apple); //三两口吃完了
      
      IFood banana = BuyBanana();
      baby.Eat(banana); //三两口吃完了
   }
   
   //买苹果
   private IFood BuyApple()
   {
      return new Apple();
   }
   
   //买香蕉
   private IFood BuyBanana()
   {
      return new Banana();
   }
   
   //打印食品列表
   //我只知道有一卡车食物,不关心类别,不关心名字,只知道有IFood对象列表
   //多态特性应用之1
   private void PrintFoods(IList<IFood> foods)
   {
      foreach (IFood food in foods)
      Console.WriteLine(food.Name); //调用接口定义的食物名称
   }
}

上一篇 下一篇