C#中的多态

 三谈多态(c#描述)——善用virtual    
  (原文是Nicrosorft用ObjectPasacl写的,感觉此文不错,遂改用c#描述,希望Nicrosorft原谅没在你同意之下,便善做主张,如果您不同意,我会删掉此贴)————BeyondCsharp  
  多态性,是一种能给程序带来灵活性的东西。看过《设计模式》的程序员应该都知道,相当多的模式(几乎所有)都是依靠多态来实现的,以此给程序提供可扩展、可重用性。在《再谈多态——向上映射及VMT/DMT》一文中,提到了多态性是依赖于虚函数/虚方法(即动态绑定)来实现的,也介绍了虚函数/虚方法(virtual)的实现方法。那么本文就来谈一下,如何使用virtual、善用virtual来获取多态性给我们带来的灵活性。  
      
    实例是最好的教材,因此本文还是假设一个需求,写一个实例来讲解。不过,我想没有必要给出所有源码,因此在本文中有些实现的代码会粗略带过。另外,本文所有代码均为C#l语言编写,实现环境为VS.NET。  
      
    假设我们要编写一个纯文本内的编辑器,也就是记事本(呵呵,别嫌例子老套,记事本程序在相当多方面都是很好的教材),编辑控件我们一般会用RichTextBox,但是它们的功能都不甚强大,也许我们目前没有,但日后会找到一个更好的第三方文本编辑控件(比如,支持语法着色的)。因此,我们必须为未来的改进留下方便之门,否则到时候再重写全部程序真是太傻了。  
      
    界面层(菜单响应、状态显示等)对文本编辑器控件的控制的代码对于所有编辑器来说都是类似的,应该可以被重用。那么就必须将界面层的代码与编辑器控件的控制代码隔离开来。  
      
    如何隔离?我们可以为所有的编辑器控件指定一个公共的接口(抽象类),界面层只看得到这个接口,只使用接口提供的功能,那么,我们更换任何编辑器控件时,都不必更改界面层的代码了。  
      
    首先,抽象出编辑器的基本操作,如:Load(打开文本)、Save(保存到文件)、Copy(复制到剪贴板)等等,将这些操作作为public方法。其次,考虑这些操作中有哪些会涉及到具体相关控件的,对于这些操作,你有两种选择:1、如果完全依赖控件本身的,可以选择将其定义为虚方法或抽象虚方法(即C++中的纯虚函数);2、如果只是有部分代码依赖控件本身的,将这部分操作抽象到一个protected的抽象虚方法中,而将相同的部分代码写在基类中,并由基类的方法中调用protected的抽象虚方法。  
      
    如果还没有完全明白,请看代码:  
          //抽象类          
          public   abstract   class   Editor  
  {  
  public   Editor()  
  {  
  }  
   
                private   string   m_FileName;  
  private   string   FileName  
  {  
  get  
  {  
  return   m_FileName;  
  }  
  set  
  {  
  m_FileName=value;  
  }  
  }  
   
  public   bool   Load(string   FileName)  
  {  
  if   (DoLoad(FileName))  
  {  
                                m_FileName=FileName;  
  }  
  return   true;  
  }  
   
  public     bool   Save()  
  {  
  this.SaveAs(m_FileName);  
  return   true;  
  }  
                  protected   abstract   bool   DoLoad(string   FileName);  
  public   abstract   bool   SaveAs(string   FileName);  
                  //   ...   其他需要的操作,由需求而定  
   
  }  
      
    好,我们来详细说明一下Editor为什么是这个样子的。其有一个私有成员,保存编辑器所对应的文件名。然后看public部分,它至少提供了三个操作:Load——从某文件中读取文本到编辑器;Save——将文本保存到文件,文件名为m_FileName所保存的;SaveAs——以指定的一个文件名保存文本。  
   
    三个操作中,SaveAs为抽象虚方法,因为它的实际动作与每个编辑控件相关,而基类本身并不知道该如何保存文件。  
   
    Save和Load都是非虚方法,其中Save的任务很简单,就是将文本以m_FileName所保存的文件名保存,其实现可以是调用抽象的SaveAs,只需要将文件名传给SaveAs即可。基类完全可以确定如何完成Save(因为它可以保证派生类实现了SaveAs),因此其为非虚方法。  
   
    而Load呢?Load操作的步骤是:1、将文本从文件中读取到编辑器控件中;2、将m_FileName的值设置为所读取的文件名。其中第一个步骤与具体编辑器控件相关,应该是由派生类去实现它,第二个步骤派生类无法实现,因为派生类看不到私有的m_FileName成员。但即使把m_FileName移到protected节中,以使派生类可以访问它,也并非好办法,因为这样的话,在实现每个派生类时,都要记住设置m_FileName的值,这不仅造成代码重复(每个派生类都要这样做),而且这不应该是派生类的义务。因为m_FileName应该是基类的内部实现,对外不可见。因此,第二个步骤应该由基类来完成。如何达成这个目的呢?  
   
    我们可以注意到,protected节中有一个DoLoad方法,它就被用来完成第一个步骤——每个编辑器控件去将文本读入编辑器。然后,DoLoad由Load方法中被选择在适当的时机调用。  
     在该派生类中,有一个私有成员,RichTextBox控件的实例。然后覆盖(override)了基类的两个抽象虚方法:DoLoad和Save。  
    其实现如下:  
  public   class   RichTextEditor:Editor  
  {  
  public   RichTextEditor()  
  {  
  m_Editor=new   System.Windows.Forms.RichTextBox();  
  //   接着完成将TMemo实例置于界面上显示出来等操作,省略  
  }  
  private   System.Windows.Forms.RichTextBox   m_Editor;  
   
  protected   override   bool   DoLoad(string   FileName)  
  {  
          m_Editor.LoadFile(FileName);  
  return   true;  
  }  
   
  public   override   bool   SaveAs(string   FileName)  
  {  
  m_Editor.SaveFile(FileName);  
  return   true;  
  }  
    //   ...其它需要的操作  
  }    
     很好,这样的实现已经可以使个部分运作正常了。如果,今后找到更好的编辑器控件,只需要从Editor派生,再实现一个XXXEditor类即可,其他部分的代码不用作任何改动。而且,具体实现的TXXXEditor类中的代码,只和具体控件本身特性相关(如:读取、保存文件的方法),而公共逻辑也已经在Editor类中实现了。  
      
    virtual的使用方法,基于笔者个人认识与经验:  
      
    1、如果基类不知道如何实现某方法(只有派生类知道),而基类的其他方法又必须使用该方法,则把该方法声明为抽象虚方法——   virtual;   abstract;(即C++的纯虚函数)。  
      
    2、如果基类能够为某方法提供一种默认实现,但派生类可能完全重写这个实现,则将该方法声明为虚方法——   virtual;并实现默认算法。  
   
    3、如果基类能够且必须提供某方法的部分的实现,而派生类必须提供另一部份的实现,则将该方法声明为非虚方法,并在基类中为其配套提供一个虚方法或抽象虚方法,以允许由基类本身调用和被派生类覆盖。犹如上例中的Load与DoLoad。  
      
    善用virtual,善用多态,你的代码将更具灵活性!   
 
<think>多态是面向对象编程的三大特性之一(封装、继承、多态)。在C#中,多态允许不同类的对象对同一消息做出响应,即同一个方法在不同对象中有不同的实现。多态主要通过继承和接口实现,分为两种形式:编译时多态(静态多态)和运行时多态(动态多态)。 ### 1. 编译时多态(静态多态) 通过方法重载(overloading)实现。在同一个类中,多个方法具有相同的名称但不同的参数列表(类型、个数或顺序不同)。编译器在编译时根据传递的参数决定调用哪个方法。 示例代码: ```csharp public class Calculator { // 方法重载:两个整数相加 public int Add(int a, int b) { return a + b; } // 方法重载:三个整数相加 public int Add(int a, int b, int c) { return a + b + c; } // 方法重载:两个双精度浮点数相加 public double Add(double a, double b) { return a + b; } } ``` ### 2. 运行时多态(动态多态) 通过虚方法(virtual)和重写(override)实现,或者通过接口实现。在基类中声明虚方法,在派生类中重写该方法。程序在运行时根据对象的实际类型调用相应的方法。 示例代码: ```csharp public class Shape { public virtual void Draw() { Console.WriteLine("绘制一个形状"); } } public class Circle : Shape { public override void Draw() { Console.WriteLine("绘制一个圆形"); } } public class Rectangle : Shape { public override void Draw() { Console.WriteLine("绘制一个矩形"); } } public class Program { static void Main() { Shape[] shapes = new Shape[3]; shapes[0] = new Shape(); shapes[1] = new Circle(); shapes[2] = new Rectangle(); foreach (Shape shape in shapes) { shape.Draw(); // 运行时多态:根据实际对象类型调用相应的方法 } } } ``` 输出: ``` 绘制一个形状 绘制一个圆形 绘制一个矩形 ``` ### 3. 多态的作用 - **代码复用和扩展**:通过继承,子类可以复用父类的代码,并可以通过重写父类的方法来扩展或修改行为。 - **接口统一**:不同对象可以通过相同的接口(基类或接口)被操作,降低耦合度。 - **灵活性**:程序可以在运行时根据对象类型动态调用方法,使代码更灵活,易于维护和扩展。 - **可替换性**:子类对象可以替换父类对象,符合里氏替换原则(LSP)。 ### 4. 抽象类和接口实现多态 除了虚方法,还可以使用抽象类和接口实现多态。 #### 抽象类示例: ```csharp public abstract class Animal { public abstract void Speak(); // 抽象方法,必须被重写 } public class Dog : Animal { public override void Speak() { Console.WriteLine("汪汪!"); } } public class Cat : Animal { public override void Speak() { Console.WriteLine("喵喵!"); } } ``` #### 接口示例: ```csharp public interface ISpeak { void Speak(); } public class Dog : ISpeak { public void Speak() { Console.WriteLine("汪汪!"); } } public class Robot : ISpeak { public void Speak() { Console.WriteLine("你好!"); } } ``` ### 总结 C#中的多态使得代码更加模块化、可扩展和易于维护。通过方法重载实现编译时多态,通过虚方法、抽象类和接口实现运行时多态。 §§相关问题§§: 1. 什么是里氏替换原则(LSP)?它在多态中如何体现? 2. 在C#中,抽象类和接口在实现多态时有什么区别? 3. 如何通过委托(delegate)实现多态? 4. 请举例说明C#中方法隐藏(new关键字)与重写(override)的区别?</think>### C#多态的作用详解 多态(Polymorphism)是面向对象编程的四大基本原则之一(封装、抽象、继承、多态),在C#中主要通过继承、虚方法(`virtual`)、抽象方法(`abstract`)和方法重写(`override`)来实现。多态的核心作用是**允许不同类的对象对同一消息做出不同响应**。 #### 多态的主要作用: 1. **代码复用性**:通过基类定义通用接口,派生类实现具体行为 2. **系统扩展性**:新增功能时只需添加新类,无需修改现有代码 3. **接口统一**:不同对象可通过相同接口进行操作 4. **运行时绑定**:程序运行时根据对象类型决定调用哪个方法 #### 实现多态的关键技术: ```csharp // 基类定义 public abstract class Shape { // 虚方法 - 可被子类重写 public virtual void Draw() { Console.WriteLine("绘制基本图形"); } // 抽象方法 - 必须被子类实现 public abstract double CalculateArea(); // 普通方法 public void DisplayType() { Console.WriteLine("我是图形类"); } } // 派生类1 public class Circle : Shape { public double Radius { get; set; } // 重写虚方法 public override void Draw() { Console.WriteLine("绘制圆形,半径:" + Radius); } // 实现抽象方法 public override double CalculateArea() { return Math.PI * Radius * Radius; } } // 派生类2 public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public override void Draw() { Console.WriteLine($"绘制矩形:{Width}x{Height}"); } public override double CalculateArea() { return Width * Height; } // 方法隐藏(使用new关键字) public new void DisplayType() { Console.WriteLine("我是矩形类"); } } // 使用多态 public class Program { public static void Main() { // 创建不同形状的集合 List<Shape> shapes = new List<Shape> { new Circle { Radius = 5 }, new Rectangle { Width = 10, Height = 20 } }; foreach (var shape in shapes) { shape.Draw(); // 多态调用 Console.WriteLine("面积: " + shape.CalculateArea()); // 多态调用 // 调用隐藏方法(非多态) shape.DisplayType(); // 调用基类方法 ((Rectangle)shape).DisplayType(); // 调用子类方法(需要显式转换) } } } ``` #### 代码解释: 1. **虚方法(`virtual`)**: - 基类中使用`virtual`声明的方法 - 派生类可选择使用`override`重写或保持默认实现 - 示例中`Draw()`方法展示了运行时多态 2. **抽象方法(`abstract`)**: - 在抽象类中声明,没有实现 - 派生类必须使用`override`实现 - 示例中`CalculateArea()`强制子类提供具体实现 3. **方法隐藏(`new`)**: - 当派生类需要定义与基类同名但行为不同的方法时使用 - 不是真正的多态,只是隐藏基类方法 - 示例中`DisplayType()`展示了方法隐藏的效果 4. **多态调用**: - 通过基类引用调用派生类实现(如`shape.Draw()`) - 实际执行哪个方法由对象类型决定(运行时类型) #### 多态的实际应用场景: 1. **插件系统**:通过统一接口扩展功能 2. **GUI框架**:不同控件响应相同事件 3. **游戏开发**:不同实体实现相同行为接口 4. **数据处理**:统一处理不同数据源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值