面向对象(Object Oriented,缩写为OO)是现代软件技术的精髓。从早期的SmallTalk到如日中天的Java,都渗透着面向对象思想。 OO具备三大特性:封装性、继承性和多态性。想掌握面向对象思想,就必须深刻理解 其三大特性。这里我尽可能少谈概念,只用一个生活中的例子和一段代码来解释它们。 一、封装性(Encapsulation) 所谓封装,就是将某些东西包装和隐藏起来,让外界没法直接使用,只能经过某些特定的方式才能访问。OO将万物都视为“对象”(Object),任何对象都具备特性和行为。咱们将其特性称为“成员变量” (MemberVarible),将其行为称之为“成员函数"(Member Function),被封装的特性只能经过特定的行为去访问。 你们都见过旅馆里经常使用的一种茶叶吧,就是用纸袋把茶叶包装起来再系是一根线。用的时候只须要将其放在水杯里泡就行。这样的好处是不会将茶叶渣和茶垢弄的满杯子都是。 好!这就是一个封装的例子。 咱们喝茶的目的是享受茶叶的香冽;因此茶叶的味道(Flavour)就是茶叶所具备的最 重要特性之一;但是咱们没法直接享受它的清香,由于被外面的纸袋“封装”起来了。惟一的办法就是“泡”(Dilute),将茶袋扔在开水中泡,它的味道就出来了,融入水中。 若是咱们把袋装茶叶看做一个对象的话,它提供了成员变量Flavour和成员函数Dilute 。而且Flavour是私有(Private)的,咱们不能直接把它吞进肚子去,而只能经过成员函 数Dilute才能享受Flavour。 下面用C++代码来描述这个例子: Class CTea { Private: Cstring m_Flavour; //味道 Cstring m_Color; //颜色 ...... //等等其它属性 Private: Void CTea(); //构造函数 Void ~CTea(); //析构函数 Public: Cstring Dilute();//沏茶 ...... //等等其它方法 } Cstring CTea::Dilute() { //怎样泡出味道来的代码 } 这就是封装。经过将对象的某些属性声明为Private隐藏起来,只能使用其提供的特定 方法去访问。 二、继承(Inheritance) 若是只是封装,那么非面向对象语言也能部分的作到。好比在C中,用结构(Struct)、 VB中用自定义类型(Type)也能封装一些变量。 OO最有吸引力的特性是继承。通俗的说后代具备祖先的某些特色就叫继承,固然后代还能够具备本身独有的特征。举个例子吧,菜刀。 菜刀(cutlery)是钢(Steel)作的,钢是一种金属(Metal),金属则是大千世界里的一种物质(Substance)。因此菜刀的一些特性能够追溯到物质具备的通常属性。正是由于这个道理,MFC中全部类均从CObject继承而来。 这就是继承。菜刀直接继承了钢的特性,钢又继承了金属的特性,......下面的代码描 述了这种复杂而有独特的继承关系: Class CSubstance { Private: int m_color; void CSubstance(); void ~CSubstance(); //......(我是学文科的,具体属性说不上来) } Class CMetal:Public CSubstance { void CMetal(); void ~CMetal(); //...... } Class CSteel:Public CMetal { void CSteel(); void ~CSteel(); //...... } Class CCutlery:Public CSteel { private: Cstring m_Blade; void CCutlery(); void ~CCutlery(); //...... Public: void Cut(); } 这里,CSubstance被称为基类(Base class),其它被称为衍生类(Derived class)。衍生类与基类是“Is kind of”的关系。子类与其祖先类之间复杂的函数调用关系不在本文讨论之列。 继承是一种树状的层次关系。子类在继承祖先类的成员变量和成员函数的同时也能够 定义本身的成员变量和成员函数。好比,Metal 除了继承了Substance的通常特性外,还具备本身的属性诸如可延展性;CCutlery在继承CSteel的特性后还具备本身的成员诸如“刀刃”(Blade)、“锋利”(Sharpness)、行为有如“切”(Cut)等。 面向对象技术是对现实生活的抽象,你能够用生活中的经验去思考程序设计的逻辑。 三、多态性(Polymorphism) 讨论多态以前先要明白什么是“虚拟”(Virtual)。C++/MFC就是用虚拟这种方式实现多态的。为何“虚拟”这个概念?看下边的例子: Class Cincect //昆虫类 { private: int m_foot; //脚的数量 ...... //其它成员变量 private: void Cincect(); void ~Cincect(); public: void Bite()//咬人 { ...... //怎样咬人的代码,好比张开嘴啃 } } 我把Bite(咬)这个动做在基类中定义为通常化动做。但是,不是全部昆虫咬 人的方法都同样(何况还有的根本就不咬人呢,好比蜻蜓),好比蚊子是用嘴那个 吸管叮人而蚂蚁是用嘴去夹。 从昆虫这个类别衍生出如下两个类别:Cant(蚂蚁)、Cmosquito(蚊子)。 class Cant :public Cincect //蚂蚁类 { ...... } class Cmosquito :public Cincect //蚊子类 { ...... } 它们都继承了Cincect的全部成员,固然也继承了Bite()这个动做。如今就有问题了: 一样继承自昆虫,当咱们使用Bite()这个动做时怎么才能区分蚂蚁和蚊子各自的独有的咬人方式呢? 方法之一是用“::”符号指明具体引用的是那一个,但这样明显失去了灵活性; 另外一种方法就是“虚拟”。使用关键字virtual将Bite()声明为虚拟函数,而后在每一个 衍生类中从新定义,描述它们各自的咬人方法,调用的时候也不会都一种结果啦。因而上边的例子能够改写为: Class Cincect //昆虫类 { private: int m_foot; //脚的数量 ...... //其它成员变量 private: void Cincect(); void ~Cincect(); public: virtual Bite(){}//咬人,但咱们只声明这个成员函数, //却让它什么动做都不作,让衍生类本身去定 //义各自的咬人方法 } class Cant :public Cincect //蚂蚁类 { ...... virtual Bite(); } Cant::Bite() { ...... //蚂蚁具体的咬人方式 } class Cmosquito :public Cincect //蚊子类 { ...... virtual Bite(); } Cmosquito::Bite() { ...... //蚊子具体的咬人方式 } 因此,虚拟的目的是只在基类中将通常化动做声明一个成员函数的原型而不作 具体定义,让衍生类本身去定义。 这就是面向对象的特征之三:多态性。基类的同一个成员在不一样的衍生类中能够具 有不一样的形态,更好地抽象和描述大千世界中的诸多“对象”。1.了解什么是多态性 2.如何定义一个虚方法 3.如何重载一个虚方法 4.如何在程序中运用多态性 面向对象程序设计中的另一个重要概念是多态性。在运行时,能够经过指向基类的指针,来调用实现派生类中的方法。 能够把一组对象放到一个数组中,而后调用它们的方法,在这种场合下,多态性做用就体现出来了,这些对象没必要是相同类型的对象。固然,若是它们都继承自某个类,你能够把这些派生类,都放到一个数组中。 若是这些对象都有同名方法,就能够调用每一个对象的同名方法。本节课将向你介绍如何完成这些事情。 1.清单9-1. 带有虚方法的基类:DrawingObject.cs using System; public class DrawingObject { public virtual void Draw() { Console.WriteLine("I'm just a generic drawing object."); } } 说明 清单9-1 定义了DrawingObject类。这是个可让其余对象继承的基类。该类有一个名为Draw()的方法。Draw()方法带有一个virtual修饰符,该修饰符代表:该基类的派生类能够重载该方法。DrawingObject类的 Draw()方法完成以下事情:输出语句"I'm just a generic drawing object."到控制台。 2.清单9-2. 带有重载方法的派生类:Line.cs, Circle.cs, and Square.cs using System; public class Line : DrawingObject { public override void Draw() { Console.WriteLine("I'm a Line."); } } public class Circle : DrawingObject { public override void Draw() { Console.WriteLine("I'm a Circle."); } } public class Square : DrawingObject { public override void Draw() { Console.WriteLine("I'm a Square."); } } 说明 清单9-2定义了三个类。这三个类都派生自DrawingObject类。每一个类都有一个同名Draw()方法,这些Draw()方法中的每个都有一个重载修饰符。重载修饰符可以让该方法在运行时重载其基类的虚方法,实现这个功能的条件是:经过基类类型的指针变量来引用该类。 3.清单9-3. 实现多态性的程序:DrawDemo.cs using System; public class DrawDemo { public static int Main(string[] args) { DrawingObject[] dObj = new DrawingObject[4]; dObj[0] = new Line(); dObj[1] = new Circle(); dObj[2] = new Square(); dObj[3] = new DrawingObject(); foreach (DrawingObject drawObj in dObj) { drawObj.Draw(); } return 0; } } 说明 清单9-3演示了多态性的实现,该程序使用了在清单 9-1 和清单9-2中定义的类。在DrawDemo类中的Main()方法中,建立了一个数组, 数组元素是DrawingObject 类的对象。该数组名为dObj,是由四个DrawingObject类型的对象组成。 接下来, 初始化dObj数组, 因为Line, Circle和Square类都是DrawingObject类的派生类,因此这些类能够做为dObj数组元素的类型。 若是C#没有这种功能,你得为每一个类建立一个数组。继承的性质可让派生对象看成基类成员同样用,这样就节省了编程工做量。 一旦数组初始化以后,接着是执行foreach循环,寻找数组中的每一个元素。在每次循环中, dObj 数组的每一个元素(对象)调用其Draw()方法。多态性体如今:在运行时,各自调用每一个对象的Draw()方法。尽管dObj 数组中的引用对象类型是DrawingObject,这并不影响派生类重载DrawingObject 类的虚方法Draw()。 在dObj 数组中,经过指向DrawingObject 基类的指针来调用派生类中的重载的Draw()方法。 输出结果是: I'm a Line. I'm a Circle. I'm a Square. I'm just a generic drawing object. 在DrawDemo 程序中,调用了每一个派生类的重载的Draw()方法。 最后一行中,执行的是DrawingObject类的虚方法Draw()。这是由于运行到最后,数组的第四个元素是DrawingObject类的对象。 小结 如今对多态性有所了解以后,你能够在派生类中,实现一个重载基类虚方法的方法。虚方法和重载的派生类方法之间的关系就体现出C#的多态性。