继承是OOP的一个方面,可以促进代码重用,更具体地说,代码重用归为两类,经典继承(‘is-a’关系)和包含/委托模型(‘has-a’关系)。is-a关系:类之间的is-a关系也就是在两个或两个以上类类型之间创建了依赖关系,经典继承的基本思想是新类可以利用既有类的功能。定义一个基类A如下:class A{ PRivate int num; public int Num { get{return num;} set{num = value;} } public A() { Console.WriteLine(Num); } public A(int aaa) {Console.WriteLine("this is A");}}
C#提供了另外一个关键字sealed来防止发生继承。如果我们将类标记为sealed,编译器将不允许我们从这个类型派生。例如,我们将B改成下面形式:sealed class B:A{}如果C类想要继承B类的话,将会产生编译错误。class C:B//错误,不能扩展sealed标记的类。(string类是个密封的类,不能扩展。){}说明:C#结构总是隐式密封的,因此,我们永远不可以从结构继承结构,从类继承结构或者从结构继承类。结构只能用于建模独立的、用户自定义的数据类型。如果希望使用is-a关系,必须使用类。
OOP的第二个支柱:继承
1、使用base关键字来控制基类的创建。
C#下,一般基本的默认构造函数会在派生类构造函数执行之前被自动调用。为了帮助优化派生类的创建,最好实现子类构造函数,来显示调用合适的自定义基类构造函数,而不是默认构造函数。class B:A{public B(int a ):base(int aaa)}说明一点,当我们为类定义增加了自定义的构造函数,默认构造函数就自动移除了。因此必要情况下,我们需要为子类重新定义默认构造函数。
2、家族的秘密:protected关键字。
我们已经知道,公共项可以在任何地方直接访问,而私有项除了定义它的类之外,不能从其他对象进行访问。C#提供了另外一个定义成员可访问性的关键字:protected。当基类定义了受保护数据或受保护成员时,它就创建了一组可以被任何后代访问的项。如A类增加如下定义,B类就可以访问这些信息。class A{protected int rC;}这样的好处,派生类不再需要使用公共方法或属性来间接访问数据了。坏处,如果派生类有权直接访问其父类内部数据,有可能会偶然绕过公共属性内设置的既有业务规则。最后要注意,对对象用户来说,受保护数据可以认为是私有的。因此,如果B b = new B();b.rc=10;就会产生错误。
我们创建一个类C,和A类的逻辑表示为A类包含C类。如果我们更新A类则就如下:class A{ protected C c= new C();}然而,如果我们要公开被包含对象的功能给外部世界,就需要委托。简单地说,委托就是增加公共成员到包含类,以便使用被包含对象的功能。例如C类如下:class C{ public void PrintC(){Console.WriteLine("this is C");}}A类如果想要使用C类的PrintC函数,需要增加如下定义class A{ public void GetPrintC() {return c.PrintC();}}嵌套类型定义:这就是‘has-a’关系的另一种说法。在C#中我们可以直接在类或结构作用域定义类型(枚举、类、接口、结构或委托),这样做,被嵌套类型会被认为是嵌套类的成员,嵌套类型可以操作其他成员一样来操作嵌套类型(可以访问其中的私有成员):public Class D{ public class C{}//公共嵌套类型可以被任何人使用 private class C{}//私有嵌套类型只可以被包含类的成员使用。}
OOP的第三个支柱:C#的多态支持
1、virtual 和override关键字。
多态为子类提供了一种方式,使其可以定义由基类定义的方法,这种过程叫做方法重写。如果基类希望定义可以由子类重写的方法,就必须使用virtual关键字标志方法:partial class A{ public virtual void vA(int aaaaa){//操作逻辑}}如果子类希望改变虚方法的实现细节,就必须使用override关键字。class B:A{public override void vA(int aaaaa){//操作逻辑}}注意,重写方法完全可以通过base关键字来自由的使用默认行为,这样我们就不需要完全重写实现vA方法的逻辑。public override void vA(int aaaa){base.vA(aaaaa);//其余的处理逻辑}
由于许多基类都是比较模糊的实体,好的设计师会防止在代码中直接创建基类的对象(实例)。在C#中我们可以使用abstract关键字来强制这种编程方式,因此创建一个抽象基类:abstract partial class A{...}此时,A a= new A();就会产生错误。尽管我们不能直接创建抽象类,但在创建其派生类时,仍然会在内存中对其进行组装。因此对抽象类来说,定义若干在分配派生类时间接调用的构造函数是很正常的。