代码改变世界

C#基础回顾之2——OOP的支柱

2008-08-01 15:49  JimLiu  阅读(472)  评论(0编辑  收藏  举报

C#基础回顾之2——OOP的支柱

接上篇C#基础回顾之1——值类型与引用类型。

OOPObject Oriented Programming,面向对象编程)是现在最普遍的编程模式,C#作为一门OOPLObject Oriented Programming Language,面向对象编程语言)自然需要有OOP特性。OOP的支柱,最公认的三种说法莫过于“封装、继承、多态”,下面说一下C#对三大支柱的实现。

封装

C#支持自定义结构类型和自定义类类型,它们都可以定义成员,成员是有修饰符的,C#提供了public, internal, protected internal, protected, private这些访问修饰符,它们的具体作用我就不在这里多说了。

通常我们不希望把类的成员字段直接暴露出来,也就是说,不应该出现public的字段。在C++JAVA中,我们通常是用getSalarysetSalary方法来获取或设置Salary字段(当然,可以不是成对出现的,取决于这个字段逻辑上的可访问性)来解决这个问题。而C#为我们提供了更为直观的方法——属性(Property)。如下面代码所示:

private int m_Salary = 1000;

public int Salary {
    
get { return m_Salary; }
    
private set { m_Salary = value; }
}

其实这里是编译器的一个小把戏,当我们声明一个属性的时候,会自动生成get_XXXXset_XXXX方法,而如果我们定义了一个名为Salary的属性,又定义了get_Salary方法,就会发生编译错误,通过ildasm查看程序集的IL代码就会发现这一点。

属性大大方便了我们对类的成员进行封装,让我们可以以公有字段的眼光来对待属性,而不是看起来比较生硬的getXXX/setXXX

继承

随着面向对象设计模式的深入人心,单纯的传统继承(is-a关系)已经不能完全满足我们的需求——更准确的说是不能很好地满足我们的需求,所以后来又引入了委托/包含模型(has-a关系)。下面分别对这两点介绍一下C#的实现。

先说说is-a关系的传统继承。如同JAVAC#并不像C++那样支持多重继承,任何一个C#类都能且至多能继承自一个基类。比如Teacher is-a Person,那么Teacher就继承了Person

但对于有些特殊的情况,比如“沙发床”即is-a沙发又is-a床,但是C#并不支持多重继承,那么到底沙发床的基类应该是沙发还是床呢?比较通用的解决方案有两种:

第一种就是使用委托/包含模型,沙发床的基类既不是沙发也不是床,而是它们共同的更高级的一层抽象——家具。但是沙发床封装了一个沙发的实例和一个床的实例,再通过他们的特点,衍生出自己新的特点——比如沙发是供人坐的,提供了Sit方法;床是供人躺的,提供了Lie方法。沙发床即能坐又能睡,理应同时提供SitLie两个方法,但是由于沙发床没有直接继承自沙发或者床,所以需要把沙发的Sit和床的Lie方法“伪装”成自己的SitLie方法。

第二种就是使用接口继承,下文将会提到。

多态

多态有一个很大的作用在于,在我们手上有一个对象的时候,我们不用关心“这个对象具体是什么”,而只用关心“这个对象能不能干我想干的事情,如果能,那么随它怎么干都行,只要能达到目的”。

C#对多态的支持,大致可分为两类——虚方法和接口。

虚方法是标记了virtual关键字的方法,当我们通过基类引用调用虚方法的时候,它会自动地寻找这个虚方法的实现,如果这个虚方法被具体类重写(override,不是重载)了,那么就会调用重写的方法,否则将调用基类的实现。

如果一个类被标记为abstract,那么这个类就是一个抽象类。抽象类不能实例化。

如果一个方法被标记为abstract,那么这个方法就是一个抽象方法。抽象方法类似于C++中的纯虚函数,与虚方法不同的是,虚方法可以不重写实现,会调用基类的实现。而如果抽象方法没有全部实现的话,那么这个类还是一个抽象类,不能实例化,只有实现了所有抽象方法,这个类才能实例化。

接口可以看做一种特殊的抽象类,它定义了若干(可以是0个!)方法和属性(注意,是属性,不是字段——如上文所说,属性其实就是方法),这些都是public的,不用声明其为public,也不可修改访问修饰符。一个类如果实现某个接口,就要实现这个接口定义的所有方法和属性,否则编译不能通过。如果没有全部实现的话,将会无法编译通过。一个类至多只能继承一个基类,但是却可以实现任意多个接口。

下面通过接口来说一下上文中提到的“沙发床”的问题。

在这里,由于不管是沙发、床、还是沙发床,我们都只关心它的功能——能坐?或者能躺?因此,ICanSit是一个定义了Sit方法的接口;ICanLie是一个定义了Lie方法的接口,沙发和床分别是这两个接口的具体实现。而沙发床既然即能坐又能趟的,当然就是同时实现了ICanSit接口和ICanLie接口啦!当我们关心能否坐的时候,沙发床以ICanSit的身份出现;当我们关心能否趟的时候,沙发床以ICanLie的身份出现,其余多余的功能,我们在局部是不需要关注的。这就是面向对象程序设计的精华之一——面向接口编程,而不是面向实现编程。

这次要说的大概就是这么点了,其中的错误希望大家指出!