面向对象的特性
各种面向对象编程语言相互有别,但都能看到它们对面向对象三大机制的支持,即: “封装、继承、多态”
– 封装,隐藏内部实现
– 继承,复用现有代码
– 多态,改写对象行为 使用面向对象编程语言(如C#),可以推动程序员以面向对象的思维来思考软件设计结构,从而强化面向对象的编程范式。
C#是一门支持面向对象编程的优秀语言,包括:各种级别的封装支持;单实现继承+多接口实现;抽象方法与虚方法重写。
封装对象的原理
封装是把类的内部隐藏起来,以防止外部世界看见的一个面向对象的概念。
1、通过只是使用那些对外公有的类成员(public标示的),可以尽可能的减少依赖
2、减少了其他人破坏程序代码的可能性
3、用户只能看到那些对外公有的成员,清晰的展现了此类对外开放的接口,易于其他类的调用。
封装性最有用的方式之一
实现方法——访问限制修饰符
public 无限制,允许任何人来访问
protected internal = protected + internal 允许程序集内部或继承访问
Internal 允许项目或程序内部的类来访问
protected 继承时子类可以对基类有完全访问权
private 只允许同一个类中的成员访问
属性和索引器也用来封装类的细节,并提供公用接口给该类的用户
继承
一个类可以有能力直接从另一个类获得其代码和数据
派生类从基类那里获得其所有的成员
例:TextBox继承自 System.Web.UI.WebControls.WebControl继承自System.Web.UI.Control
如何实现继承?
public class InsuranceAccount : Account
C# 中只支持单继承
防止继承
public sealed class YY
... {
private int x;
public int X
...{
get ...{ return x; }
set ...{ x = value; }
}
private int y;
public int Y
...{
get ...{ return y; }
set ...{ y = value; }
}
public Point(int x, int y)
...{
this.x = x;
this.y = y;
}
}
lass Point3D:Point
... {
private int z;
public int Z
...{
get ...{ return z; }
set ...{ z = value; }
}
public Point3D(int x, int y, int z)
: base(x, y)
...{
this.Z = z;
}
}
程序讲解
类的声明可能通过在类名后加上冒号和基类的名字来指定一个基类译注4。省略基类等同于直接从object类派生。在下面的示例中,Point3D的基类是Point,而Point的基类是object:
Point3D类继承了其基类的成员。继承意味着类将隐式地包含其基类的所有成员(除了基类的构造函数)。派生类能够在继承基类的基础上增加新的成员,但是它不能移除继承成员的定义。在前面的示例中,Point3D类从Point类中继承了x字段和y字段,并且每一个Point3D实例都包含三个字段x,y和z。
如何访问基类成员?
派生类可以调用基类的方法
通过使用base关键字
派生类在访问基类的时候有一定的限制,不能访问 private 的成员;internal的基类成员只能被同一个程序集中的派生类访问
... {
public double balance;
public bool ;
public Withdraw(double amt)
...{
balance -= amt;
return true;
}
}
public class CheckAccount:Account
... {
public bool Withdraw(double amt)
...{
if(amt<= base.balance)
return base.Withdraw(amt);
else
return false;
}
}
调用基类构造函数
因为不能继承构造方法,派生类必须实现自己的构造方法
public child(int a,int b,int c):base(a,b)
{
}
多态性(1)
面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的引用,来调用实现派生类中的方法。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类覆写基类中的虚函数型方法来实现。
在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。
“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。
多态性通过派生类覆写基类中的虚函数型方法来实现。
多态性(2)
编译时的多态性
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
运行时的多态性
运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。
C#中,运行时的多态性通过覆写虚成员实现。
虚拟函数
声明虚方法
使用virtual关键字 public virtual bool Withdraw(…);
调用虚方法,运行时将确定调用对象是什么类的实例,并调用适当的覆写的方法。
虚方法可以有实现体
具体的检查的流程如下:
1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
2、如果不是虚函数,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。
3、在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。
... {
protected internal int a=0;
public virtual void Func() // 注意virtual,表明这是一个虚拟函数
...{
Console.WriteLine("Func In A");
}
}
class B : A // 注意B是从A类继承,所以A是父类,B是子类
... {
public override void Func() // 注意override ,表明重新实现了虚函数
...{
Console.WriteLine("Func In B");
}
}
class C : B // 注意C是从A类继承,所以B是父类,C是子类
... {
}
class D : A // 注意B是从A类继承,所以A是父类,D是子类
... {
public new void Func() // 注意new ,表明覆盖父类里的同名类,而不是重新实现
...{
Console.WriteLine("Func In D");
}
}
static void Main( string [] args)
... {
//Point3D p3d = new Point3D(1, 2, 3);
//Console.WriteLine("x:{0},y:{1},z:{2}", p3d.X, p3d.Y, p3d.Z);
A a; // 定义一个a这个A类的对象.这个A就是a的申明类
A b; // 定义一个b这个A类的对象.这个A就是b的申明类
A c; // 定义一个c这个A类的对象.这个A就是b的申明类
A d; // 定义一个d这个A类的对象.这个A就是b的申明类
a = new A(); // 实例化a对象,A是a的实例类
b = new B(); // 实例化b对象,B是b的实例类
c = new C(); // 实例化b对象,C是b的实例类
d = new D(); // 实例化b对象,D是b的实例类
Console.WriteLine("a.Func() ");
a.Func();
/**///// 执行a.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类A,就为本身 4.执行实例类A中的方法 5.输出结果 Func In A
Console.WriteLine("b.Func() ");
b.Func();
/**///// 执行b.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类B,有重载的 4.执行实例类B中的方法 5.输出结果 Func In B
Console.WriteLine("c.Func() ");
c.Func();
/**///// 执行c.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类C,无重载的 4.转去检查类C的父类B,有重载的 5.执行父类B中的Func方法 5.输出结果 Func In B
Console.WriteLine("d.Func() ");
d.Func();
/**///// 执行d.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类D,无重载的(这个地方要注意了,虽然D里有实现Func(),但没有使用override关键字,所以不会被认为是重载) 4.转去检查类D的父类A,就为本身 5.执行父类A中的Func方法 5.输出结果 Func In A
D d1 = new D();
d1.Func(); // 执行D类里的Func(),输出结果 Func In D}
// Demo.多态.Shoot.ShootBird(new Demo.多态.Sparrow());
}
什么时候用到虚函数
既然虚函数应为多态而生,那么简单的说当我们在C#中要想实现多态的方法之一就是使用到虚函数。复杂点说,那就是因为OOP的核心思想就是用程序语言描述客观世界的对象,从而抽象出一个高内聚、低偶合,易于维护和扩展的模型。
但是在抽象过程中我们会发现很多事物的特征不清楚,或者很容易发生变动。
比如飞禽都有飞这个动作,但是对于不同的鸟类它的飞的动作方式是不同的,有的是滑行,有的要颤抖翅膀,虽然都是飞的行为,但具体实现却是千差万别,在我们抽象的模型中不可能把一个个飞的动作都考虑到,那么怎样为以后留下好的扩展,怎样来处理各个具体飞禽类千差万别的飞行动作呢?比如我现在又要实现一个类“鹤”,它也有飞禽的特征(比如飞这个行为),如何使我可以只用简单地继承“飞禽”,而不去修改“飞禽”这个抽象模型现有的代码,从而达到方便地扩展系统呢?
因此面向对象的概念中引入了虚函数来解决这类问题。
使用虚函数就是在父类中把子类中共有的但却易于变化或者不清楚的特征抽取出来,作为子类需要去重新实现的操作(override)。而虚函数也是OOP中实现多态的关键之一。