一. 面向对象编程的含义
1. 面向对象编程(Object-Oriented Programming,OOP)
前面介绍的编程方法称为函数(或过程)化编程,而OOP技术,常常要使用许多代码模块,每个模块都提供特定的功能,每个模块都是孤立的,甚至与其他模块完全独立。
这种模块化编程方法提供了非常大的多样性,大大增加了重用代码的机会。数据表达方式的一致性和简化任务都是OOP的特点。
2. 对象的含义
对象的类型在OOP中有一个特殊的名称:类
注意“类”和“对象”是完全不同的概念
“类的实例”和对象含义相同
例如,类是指汽车的模板,或者用于构建汽车的规则。汽车本身是这些规划的实例,所以可以看作对象
统一建模语言(Unified Modeling Language,UML)是为应用程序建模而设计的
左图是打印机类Printer的UML表示方法,类名显示在这个框的顶部
右图是这个Printer类的一个实例myPrinter,顶部,实例名显示在前面,后面是类名,这两个名称用一个冒号分隔
①属性和字段
属性与字段是不同的,因为属性不提供对数据的直接访问
一般情况下,在访问状态时最好提供属性,而不是字段,因为这样可以更好地控制各种行为,这个选择不会影响使用对象实例的代码,因为使用属性和字段的语法是相同的。
常见的情况是把字段设置为私有,通过公共属性访问它们。这样,类中的代码就可以直接访问存储在字段中的数据,而公共属性禁止外部用户访问这些数据,以防他们在其中放置无效的内容。公共成员是类可以访问的成员。
私有字段和属性可以看作是拥有它们的对象的内部成员,而公共字段和属性的作用域也包括对象以外的代码。
在类的UML表示方法中,用第二部分显示属性和字段
这是CupOfCoffee类的表示方式,前面为它定义了5个成员(属性或字段,在UML中,它们没有区别)。
每个成员都包含下述信息:
A. 可访问性,+号表示公共成员,-号表示私有成员。但一般情况下,本章的图中不显示私有成员,因为这些信息是类内部的信息。至于读/写访问,则不提供任何信息。
B. 成员名。
C. 成员的类型。
冒号用于分隔成员名和类型。
② 方法
“方法”这个术语用于表示对象中的函数。
方法用于提供访问对象的功能,也可以是公共的或私有的,按照需要限制外部代码的访问。
在UML的对象框中,方法显示在第三部分
其语法类似于字段和属性,但最后显示的类型是返回类型,在这一部分,还显示了方法的参数。
在UML中,每个参数都带有下述标识符之一:in、out或inout。
它们用于表示数据流的方向,其中out和inout大致对应于第6章讨论的C#关键字out和ref。in大致对应于C#中不使用这两个关键字的情形。
3. 一切皆对象
C#和.NET Framework中的所有东西都是对象
控制台应用程序中的Main()函数就是类的一个方法
前面介绍的每个变量类型都是一个类
前面使用的每个命令都是一个属性或方法,例如,<String>.Length和<String>.ToUpper()等。句点字符把对象实例名和属性或方法名分隔开来,方法名后面的()把方法与属性区分开来
4. 对象的生命周期
构造阶段:对象最初进行实例化的时期。这个初始化过程称为构造阶段,由构造函数完成。
析构阶段:在删除一个对象时,常常需要执行一些清理工作,例如,释放内存,这由析构函数完成。
①构造函数
构造函数就是用于初始化数据的函数。所有的类定义都至少包含一个构造函数。
在这些构造函数中,可能有一个默认的构造函数,该函数没有参数,与类同名。
类定义还可能包含几个带有参数的构造函数,称为非默认的构造函数。
在C#中,用new关键字来调用构造函数
用下面的方式通过其默认的构造函数实例化一个CupOfCoffee对象:
CupOfCoffee myCup = new CupOfCoffee();
用非默认的构造函数来创建对象。例如,CupOfCoffee类有一个非默认的构造函数,它使用一个参数在初始化时设置咖啡豆的品牌:
CupOfCoffee myCup = new CupOfCoffee("Blue Mountain");
构造函数与字段、属性和方法一样,可以是公共或私有的。
在类外部的代码不能使用私有构造函数实例化对象,而必须使用公共构造函数。
这样,就可以要求类的用户使用非默认的构造函数(把默认构造函数设置为私有的)。
一些类没有公共的构造函数,外部的代码就不可能实例化它们,这些类称为不可创建的类
②析构函数
.NETFramework使用析构函数清理对象。
5. 静态和实例类成员
属性、方法和字段等成员是对象实例所特有的,此外,还有静态成员。
静态成员可以在类的实例之间共享,所以可以将它们看作是类的全局对象。
静态属性和静态字段可以访问独立于任何对象实例的数据,静态方法可以执行与对象类型相关、但与对象实例无关的命令。
在使用静态成员时,甚至不需要实例化对象
例如,前面使用的Console.WriteLine()和Convert.ToString()方法就是静态的,根本不需要实例化Console或Convert类
许多情况下,静态属性和方法有很好的效果。例如,可以使用静态属性跟踪给类创建了多少个实例。
在UML语法中,类的静态成员用下划线表示,如下图
①静态构造函数
使用静态构造函数可以执行一些复杂的初始化任务。
一个类只能有一个静态构造函数,该构造函数不能有访问修饰符,也不能带任何参数。
静态构造函数不能直接调用,只能在下述情况下执行
A. 创建包含静态构造函数的类实例时
B. 访问包含静态构造函数的类的静态成员时
在这两种情况下,会先调用静态构造函数,之后实例化类或访问静态成员。无论创建了多少个类实例,其静态构造函数都只调用一次。
为了区分静态构造函数和其它构造函数,也将所有非静态构造函数称作实例构造函数。
②静态类
静态类只能包含静态成员,不需要实例构造函数,因为按照定义,它根本不能实例化。
但静态类可以有一个静态构造函数。
二. OOP技术
1. 接口
接口是把公共实例(非静态)方法和属性组合起来,以封装特定功能的一个集合。
一旦定义了接口,就可以在类中实现它。这样,类就可以支持接口所指定的所有属性和成员。
①接口不能单独存在。不能像实例化一个类那样实例化接口。
②接口不能包含实现其成员的任何代码,而只能定义成员本身。实现过程必须在实现接口的类中完成。
在UML中,在对象上实现的接口用“棒棒糖”语法来表示。如下图中,用与类相似的语法把IHotDrink的成员放在一个单独的框中。
一个类可以支持多个接口,多个类也可以支持相同的接口。
在发布接口后,最好不要修改它。
A. 可删除的对象
支持IDisposable接口的对象必须实现其Dispose()方法,即它们必须提供这个方法的代码。
当不再需要某个对象时,就调用这个方法,释放重要的资源,否则,该资源会等到对垃圾回收调用析构方法时才释放。
这样可以更好地控制对象所使用的资源。
using关键字可以在代码块中初始化使用重要资源的对象,会在这个代码块的末尾自动调用Dispose()方法:
<ClassName> <VariableName> = new <ClassName>();
...
using (<VariableName>)
{
...
}
或者把初始化时象<VariableName>作为using语句的一部分:
using (<ClassName> <VariableName> = new <ClassName>())
{
...
}
2. 继承
任何类都可以从另一个类中继承,这就是说,这个类拥有它继承的类的所有成员。
C#中的对象仅能直接派生于一个基类,当然基类也可以有自己的基类。
继承性可以从一个较一般的基类扩展或创建更多的特定类。
在UML中,用箭头表示继承,如下图
一个代表农场家禽的类叫作Animal,拥有EatFood()或Breed()等方法,我们可以创建一个派生类Cow,支持所有这些方法,它也有自己的方法,如Moo()和SupplyMilk()。还可以创建另一个派生类Chicken,该类有Cluck()和LayEgg()方法。
派生类不能访问基类的私有成员,但可以访问其公共成员。
第三种可访问性:protected,只有派生类才能访问protected成员。外部代码不能访问private成员和protected成员。
基类的成员可以是虚拟的,也就是说,成员可以由继承它的类重写。
在前面的家畜示例中,可以把EatFood()变成虚拟成员,在派生类中为它提供新的实现代码,例如为Cow类提供新实现代码:
基类还可以定义为抽象类。抽象类不能直接实例化。要使用抽象类,必须继承这个类,实现代码必须在派生类中提供:
假如Animal是一个抽象类,UML就会如下图所示:(抽象类名以斜体显示,有时它们的方框有一个短横线)
不能实例化抽象类,并不意味着不能在抽象类中封装功能。
类可以是密封(seal)的。密封的类不能用作基类,所以没有派生类。
在C#中,所有的对象都由一个共同的基类object(在.NET Framework中,它是System.Object类的别名)。
如前面所述,接口也可以继承自其他接口。与类不同的是,接口可以继承多个基接口(与类可以支持多个接口的方式类似)。
3. 多态性
4. 对象之间的关系
5. 运算符重载
6. 事件
7. 引用类型和值类型
三. Windows应用程序中的OOP