c# 基础知识 2

面向对象的三大特征之一: 封装

  具体来说,封装隐藏了类内部的具体实现细节,对外则提供统一访问接口,来操作内部数据成员。这样实现的好处是实现了UI分离,程序员不需要知道类内部的具体实现,只需按照接口协议进行控制即可。同时对类内部来说,封装保证了类内部成员的安全性和可靠性。

  也就是说,封装就是一个包装,将包装的内外分为两个空间,对内实现数据私有,对外实现方法调用,保证了数据的完整性和安全性

  先了解下字段、属性和方法

  1.字段

  字段(field)通常定义为private,表示类的状态信息。CLR支持只读和读写字段。值得注意的是,大部分情况下字段都是可读可写的,只读字段只能在构造函数中被赋值,其他方法不能改变只读字段。常见的字段定义为:

public class Client
{ 
  private string name; //用户姓名   private int age; //用户年龄   private string password; //用户密码 }

  类的字段信息最好以私有方式提供给类的外部,而不是以公有方式来实现,否则不适当的操作将造成不必要的错误方式。

  那么,如上文所言,将字段设置为private后,对对象状态信息的控制又该如何实现呢?同时我们也期望除了实现对数据的访问,最好能加入一定的操作,达到数据控制的目的。因此,面向对象引入了另一个重量级的概念:属性。

  2.属性

  属性(property)通常定义为public,表示类的对外成员。属性具有可读、可写属性,通过get和set访问器来实现其读写控制。例如上文中Client类的字段,我们可以相应地封装其为属性:

public class Client
{
  private string name; //用户姓名
  public string Name 
  {
    
get { return name; }     set
    {       name = value == null ? String.Empty : value;     } }
  
private int age; //用户年龄   public int Age
  {     
get { return age; }     set
    {       
if ((value > 0) && (value < 150))       {         age = value;       }       else       {         throw new ArgumentOutOfRangeException ("年龄信息不正确。");       }
    }   }
}

  当我们再次以 xiaoWang.Age = 1000; 这样的方式来实现对小王的年龄进行写控制时,自然会弹出异常提示,从而达到了保护数据完整性的目的。

3.方法

  方法(method)封装了类的行为,提供了类的对外表现。用于将封装的内部细节以公有方法提供对外接口,从而实现与外部的交互与响应。例如,从上面属性的分析我们可知,实际上对属性的读写就是通过方法来实现的。因此,对外交互的方法,通常实现为public。

  当然不是所有的方法都被实现为public,否则类内部的实现岂不是全部暴露在外。必须对对外的行为与内部操作行为加以区分。因此,通常将在内部的操作全部以private方式来实现,而将需要与外部交互的方法实现为public,这样既保证了对内部数据的隐藏与保护,又实现了类的对外交互。例如在ATM类中,对钱的计算、用户验证这些方法涉及银行的关键数据与安全数据的保护问题,必须以private方法来实现,以隐藏对用户不透明的操作,而只提供返回钱款这一public方法接口即可。在封装原则中,有效地保护内部数据和有效地暴露外部行为一样关键。

总结:

  字段通常定义为private,属性通常实现为public,而方法在内部实现为private,对外部实现为public,从而保证对内部数据的可靠性读写控制,保护了数据的安全和可靠,同时又提供了与外部接口的有效交互。这是类得以有效封装的基础机制。

  装性旨在保证:

  1. 隐藏系统实现的细节,保证系统的安全性和可靠性。
  2. 提供稳定不变的对外接口。因此,系统中相对稳定部分常被抽象为接口。
  3. 封装保证了代码模块化,提高了软件的复用和功能分离。 

面向对象的三要素之一: 多态

  1、多态的分类

  根据其实现的方式我们可以进一步分为基类继承式多态和接口实现式多态。

  (1)基类继承多态

  基类继承多态的关键是继承体系的设计与实现,在FileLoader系统中File类作为所有资料类型的基类,然后根据需求进行逐层设计,我们从架构设计图中可以清楚地了解继承体系关系。在客户端调用时,多态是以这种方式体现的:

  Files myFile = new WORDFile();

  myFile.Open();

  myFile是一个父类Files变量,保持了指向子类WORDFile实例的引用,然后调用一个虚方法Open,而具体的调用则决定于运行时而非编译时。从设计模式角度看,基类继承式多态体现了一种IS-A方式,例如WORDFile IS-A Files就体现在这种继承关系中。

  (2)接口实现式多态

  多态并非仅仅体现在基于基类继承的机制中,接口的应用同样能体现多态的特性。区别于基类的继承方式,这种多态通过实现接口的方法约定形成继承体系,具有更高的灵活性。从设计模式的角度来看,接口实现式多态体现了一种CAN-DO关系。同样,在万能加载器的客户端调用时,也可以是这样的实现方式:

   IFileOpen myFile = new WORDFile();

  myFile.Open();

  当然,很多时候这两种方式都是混合应用的,就像本节的FileLoader系统的实现方式。

  2、多态的运行机制

  从技术实现角度来看,是.NET的动态绑定机制成就了面向对象的多态特性。那么什么是动态绑定,.NET又是如何实现动态绑定呢?

  动态绑定,又叫晚期绑定,是区别与静态绑定而言的。静态绑定在编译期就可以确定关联,一般是以方法重载来实现的;而动态绑定则在运行期通过检查虚拟方法表来确定动态关联覆写的方法,一般以继承和虚方法来实现。在.NET中,虚方法以virtual关键字来标记,在子类中覆写的虚方法则以override关键字标记。从设计角度考量,通常将子类中共有的但却容易变化的特征抽取为虚函数在父类中定义,而在子类中通过覆写来重新实现其操作。

  关于.NET通过什么方式来实现虚函数的动态绑定机制,在此,我们提取万能加载器FileLoader中的部分代码,来深入分析通过虚方法进行动态绑定的一般过程:

abstract class Files: IFileOpen 
{ 
  public abstract void Open();
  public void Delete()
  {
     //实现对文件的删除处理
  } 
} 
abstract class DocFile: Files
{ 
  public int GetPageCount()
  { 
    //计算文档页数 
  } 
}
class WORDFile : DocFile 
{ 
  public override void Open() 
  { 
    Console.WriteLine("Open the WORD file.");
  }
}

  在继承体系的实现基础上,接着是客户端的实现部分:

   Files myFile = new WORDFile();

   myFile.Open();

   针对上述示例,具体的调用过程,可以小结为:

  编译器首先检查myFile的声明类型为Files,然后查看myFile调用方法是否被实现为虚方法。如果不是虚方法,则直接执行即可;如果是虚方法,则会检查实现类型WORDFile是否重写该方法Open,如果重写则调用WORDFile类中覆写的方法,例如本例中就将执行WORDFile类中覆写过的方法;如果没有重写,则向上递归遍历其父类,查找是否覆写该方法,直到找到第一个覆写方法调用才结束。

  3.多态的规则和意义

  • 多态提供了对同一类对象的差异化处理方式,实现了对变化和共性的有效封装和继承,体现了“一个接口,多种方法”的思想,使方法抽象机制成为可能。
  • 在.NET中,默认情况下方法是非虚的,以C#为例必须显式地通过virtual或者abstract标记为虚方法或者抽象方法,以便在子类中覆写父类方法。
  • 在面向对象的基本要素中,多态和继承、多态和重载存在紧密的联系,正如前文所述多态的基础就是建立有效的继承体系,因此继承和重载是多态的实现基础。

 

posted @ 2016-04-07 11:19  梦幽蓝天玉  阅读(150)  评论(0编辑  收藏  举报