高级面向对象程序与设计的概念

1.1     简介

上一章中,我们讨论了C#实现基本OOP的概念。本章,我们将继续深入面向对象编程的概念,如多态性和虚函数、抽象基类、接口等。

1.2     C#中的多态性

C#多态性是指同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。有了多态性,在运行时就能方便实现派生类的方法。虚函数和多态性的关系很密切,虚函数允许派生类完全或部份重写基类函数的功能。下面我们来研究一段代码。

代码段1:

public class ShapeObj

{

public virtual void area()

{

System.Console.WriteLine(“这是一个虚area方法”);

}

}

在代码段1中,我们创建了一个类ShapeObj,并定义了一个称为area()的虚方法。area()方法在屏幕上输出一条消息。area()方法是一个虚方法,语法相当类似于通常的方法,但是必须指定virtual关键字。当使用此关建字后,不允许使用static、abstract、或override修饰符。从基类的对象调用派生类的方法时,需要在派生类中对基类的虚函数进行重载。在派生类中重新定义此虚函数时,方法名、返回值类型、参数个数、类型、顺序都必须与基类中的虚函数完全一致。派生类中声明加上override关建字,不允许有new,static或virtual修饰符。

图示:

public class Base //基类

{

public virtual void Func() //虚函数

{

Console.WriteLine(“Func of Base”);

}

}

public class Derived : Base //派生类

{

public override void Func()

{

Console.WriteLine(“Func of Derived”);

}

}

public static void Main()

{

Base B= new Derived();

B.Func();

}

Base类对象调用Func()也将调用Derived类的Func(),因为虚方法已被重写。

代码段2:

puhlic class Circle:ShapeObj

{

public override void area()

{

System.Console.WriteLine(“这是Circle的area()方法”);

}

}

public class Rectangle:ShapeObj

{

public override void area()

{

System.Console.WriteLine(“这是Rectangle的Area()方法”);

}

}

public class Square:ShapeObj

{

public override void area()

{

System.Console.WriteLine(“这是Square的area()方法”);

}

}

上面的代码段2中,定义了三个类(Rectangle、Circle和Square)。这些类是从ShapeObj类派生的,并且重写了ShapeObj类的area()方法。override关建字用于重写基类函数area()。下面我们将编写程序的Main()函数。这个函数将把代码的所有这些类组合到一起。

代码段3:

public class PolymorphismExp

{

public static void Main()

{

ShapeObj[] objArray = new ShapeObj[4];

objArray[0]=new ShapeObj();

objArray[1]=new Rectangle();

objArray[2]=new Circle();

objArray[3]=new Square();

foreach(ShapeObj iterateArray in objArray)

{

iterateArry.area();

}

}

}

以上示例输出结果为:

这是一个虚area方法

这是Rectangle的area方法

这是Circle的area()方法

这是square的area()方法

可以看到,因为它们之间存在继承关系,所以可以把它们作为继承类型添加到数组中。这样他们都拥有相同的area方法。就可以调用每个对象的该方法。如果不这样,我们需要为每个对象创建不同的数组。大量增大了编程工作。

示例2:

class SecondExp

{

public int firstMethod()

{

return(secondMethod() * thirdMethod());

}

public virtual int secondMethod()

{

return(10);

}

public int thirdMethod()

{

return(20);

}

}

class DerivedClass : SecondExp

{

public override int secondMethod()

{

return(30);

}

}

class Test

{

public static void Main()

{

DerivedClass objDerived =new DerivedClass();

System.Console.WriteLine(objDerived.firstMethod());

}

}

最后输出结果:600

请注意,在DerivedClass类中,没有重写firsMethod()方法时,从DerivedClass类的对象调用firstMethod()时,将调用SecondExp类的firstMethod(),这是因为DerivedClass类继承了SecondExp类。

现在,SecondExp类的firstMethod() 需要调用secondMethod()。在SecondExp类和DerivedClass类中都有secondMethod()。

要是采用通常的方法重写,并且不将有关的重写方法定义为虚方法,那么被调用的将会是SecondExp类的secondMethod(),并且输出将为200。

将SecondExp类的secondMethod()指定为虚函数,并在DerivedClass类中重写SecondExp类的secondMethod()方法。

由于对firstMethod()调用来自DerivedClass,因此将调用DerivedClass类中的重写方法。

多态性不仅仅是重写,而且是智能化重写。

重写和多态性之间的区别在于,调用哪种方法的决定是在运行时作出的。(基类的方法,还是实例化对象的类的方法)

多态性需要虚函数,而虚函数则需要方法重写,这就是多态性与重写之间的联系。

在实现多态的情型下,派生类的可访问性必须低于或等于基类的可访问性。

1.3     抽像基类

在某些时候,我们只需要对继承某个特定类,但不需要实例化该类的对象。这样的类称为抽像类。C#的抽像类以abstract修饰来标示。抽象基类不能实例化。在抽象基类中可以指定一个方法而不实现其代码主体。这意味着抽象基类保存着方法定义,而方法的实现则写在派生类中。这种没有实现的方法称为操作。我们来研究下示例。

示例3:

using System;

abstract class BaseClass

{

public abstract void abstractFunc();

public void nonAbstractFunc()

{

Console.WriteLine(“这是nonAbractFunc()方法!”);

}

}

class DerivedClass:BassClass

{

public override void abstractFunc()

{

Console.WriteLine(“这是abstractFunc()方法”);

}

}

class Test

{

static void Main()

{

DerivedClass objDerived=new DerivedClass();

BassClass objBase=objDerived;

objDerived.abstractFunc();

objDerived.abstractFunc();

}

}

显示结果为:这是abstractFunc()方法, abstractFunc()方法

上面的示例3中,声明了一个名为BaseClass的抽象基类。

抽象基类除了包含抽象方法(操作)外,还可以包含已实现的方法。

操作需要用abstract关键字来标记。

操作的定义始终以分号结束。

1.4     接口

上一节中我们说过了抽象类可以同时具有抽象方法和非抽象方法。如果需要定义只包含抽象方法的类,也就是纯抽象基类,我们就可以创建接口。

即,接口与纯抽象基类相似。它只能包含抽象方法,而不能包含方法的实现。记住,接口不能创建实例。接口拿来干嘛用呢?接口仅用于指示实现特定接口的类必须实现该接口所列出的成员。也就是接口是对类中方法、属性、事件或索引器的约束条件。

下面我们来研究下代码段。

代码段4;

public interface IFile

{

int delFile();

void disFile();

}

接口我们使用interface关键字来定义。接口名通常以大写字母I开头,以表示它是一个接口。

接口的成呗没有访问修饰符;这些修饰由类继承,以设置其可见性。接口的成员必须是方法、属性、事件或索引器,接口不能包含常数、字段、运算符、实例构告函数或者类型、也不能包含静态成员。

当一个类使用一个接口时,我们通常称之为这个类“实现”了该接口。

代码段5:

public class MyFile:IFile

{

public int defile()

{

System.Console.WriteLine(“删除文件实现!”);

return(0)

}

public void disFile()

{

System.Console.WriteLine(“自动获取文件成功!”);

}

}

Class Test

{

static void Main()

{

MyFile objMyFile = new MyFile();

objMyFile.disFile();

int retValue = objMyFile.delFile();

}

}

输出结果为:删除文件成功! 自动获取文件成功!

注意:类MyFile实现了接口IFile,与继承一样,这里使用的是“:”操作符。

在大括号中,定义了接口中方法的实现。这里有一件很有意思的事件:与抽象基类不同我们没有重写方法,而直接实现了它们,因此根本不需要指定关键字override。

Main()方法中实例化类的方式以及调用这些方法的方式没有改变。

类可以实现接口,也可以继承其他类。

下面我们来看个例子。

代码段 6

using System;

public class BaseforInterface

{

public void open()

{

Console.WriteLine(“这是BaseforInterface的open方法”);

}

}

现在,我们让MyFile类来继承这个类,同时实现IFile接口

代码段7

using System

public class MyFile:BaseforInterface , IFile

{

public int delFile()

{

Console.WriteLine(“DelFile实现!”);

return(0);

}

public void disFile()

{

Console.WriteLine(“DisFile实现!”);

}

}

class Test

{

static void Main()

{

MyFile objMyFile = new MyFile();

ojMyFile.disFile();

int retValue = objMyFile.defile();

objMyFile.open();

}

}

输出结果为:DsFile实现! DelFile实现! 这是BaaseforInterface的open方法

注意:如果需要继承类并同时实现接口,我们以逗号区分。

1.4.1  多接口实现

C#中不允许有多重继承的。但是它允许多接口实现。也就是一个类可以实现多个接口。下面我们来研究下代码。

代码段8:

public interface IFileTwo

{

void applySecondInterface();

}

下面我们用MyFile类实现这个接口。

代码段9:

using System;

public class MyFile:BaseforInterface,IFile,IFileTwo

{

public int delFile()

{

Console.WriteLine(“DelFile实现!”);

return (0)

}

public void disFile()

{

Console.WriteLine(“DisFile实现!”);

}

public void applySecondInterface()

{

Console.WriteLine(“ApplySecondInterface实现!”);

}

}

class Test

{

static void Main()

{

MyFile objMyFile = new MyFile();

objMyFile.disFile();

int retValue=objMyFile.delFile();

objMyFile.open();

objMyFile.applySecondInterface();

}

}

输出结果为:

DisFile实现!

DelFile实现!

这是BaseforInterface的open方法

ApplySecondInterface实现!

只要不存在命名冲突,C#中完全接受多接口实现。如果出现相同命名包括类型和参数,则会产生问题,这样将无法指定要实现哪个接口。

但,我们可以通过显式接口实现来解决这个问题。

1.4.2  显式接口实现

显式接口实现非常简单。我们来看一个例子。

代码段10:

public interface IFile

{

int delFile();

void disFile();

}

public interface IFileTwo

{

void applySecondInteface();

void disFile();

}

public class MyFile: BaseforIntface,IFile,IFileTow

{

….

void IFile.disFile()

{

System.Console.WriteLine(“DisFile的IFile实现”);

}

void IFileTwo.disFile()

{

System.Console.WriteLine(“DisFile的IFileTwo实现”);

}

}

1.4.3  接口继承

接口具有不变性,但这并不意味着接口不再发展。和类相似接口也可以继承和发展。接口继承和类的继承不同,首先,类继承不仅是说明继承,而且也是实现继承;而接口继承只是说明继承。也就是说,派生类可以继承基类的方法实现,而派生的接口只继承了父接口的成员方法说明。其次,C#中类继承只允许单继承,但是,接口继承允许多继承,一个子接口可以有多个父接口。接口可从零或多个接口中继承。从多个接口中继承时,用“:”后跟被继承的接口的名字,多个接口的名字间用逗号分割。被继承的接口应该是可以访问得到的,比如从private 类或internal类型的接口中继承就是不允许的。接口不允许直接或间接的从自身继承。可以将多个接口组合到一起来创建新的接口。其语法非常类似于继承的语法,不同之处在于可以合并多个接口来形成单个接口。

假定需要将两个接口IFile和IFileTwo合并为一个接口IallFile,需要编写以下代码。

代码段11:

interface IAllFile:IFile,IFileTwo

{

//如果需要,除了IFile和IFileTwo操作之外,还可以添加更多操作。

}

1.5     小结

多态性和虚函数关系非常密切

需要从基类的对象调用派生类方法时,可以使用虚函数。

多态性不只是重载或重写,而是智能重写。

重写和多态性之间的区别在于,在多态性中,要调用哪个方法的决定是在运行时做出的。

多态性需要虚函数,而虚函数则需要方法重写。

抽象基类是至少包含一个抽象成员,不包括实现方法的类。

不能创建抽象基类的新实例。

没有实现的方法称为操作。

接口是纯抽象基类。它只能包含抽象方法,而不能包含任何方法的实现。

一类可以实现多个接口:事实上,类能够从另一个类继承,也能够实现接口。

1.6     练习

1. 请观察下面的代码并找出其中的错误,选择一个列出了代码中全部错误的正确答案。

Class SecondExp

{

public int firstMethod()

{

return (100);

}

}

class DerivedClass:SecondExp

{

public override int secondMethod()

{

return(399);

}

}

class Test

{

public static void Main()

{

DerivedClass objDerived = new DerivedClass();

System.Console.WriteLine(objDerived.firstMethod());

}

}

a. 类SeconExp中没有将任何方法标记为进行重写。

b. 没有找到合适的方法进行重写

c. A和B都是

2. 抽象基类不能包含方法实现

a. 对 b.错

3. 抽象基类________________实例化。

a.可以 b.不可以

4. _______________可以看作类的模具。

a.抽象类 b.接口 c.虚方法

5. 在C#中,不允许多接口实现。

a.对 b.错

3.7 作业

1.创建一个称为Animals的抽象类。它应当包含一个称为saySomething()的方法,该方法既不返回任何类型,也不带任何参数。接下来,从Animals派生一个称为Cats的类。在派生的类中实现saySomething()方法。SaySomeghing()必须在用户的控制台上打印以下信息:

猫喵喵喵叫!

从Animals抽象基类派生另一个称为Dogs类。这一次saySomething()方法需要在用户的控制台上打印以下消息:

狗汪汪叫!

2.重复上面的练习,但是这次不要创建抽象基类,而是通过使用接口达到相同的效果。

3. 创建一个称为Imammals的接口,该接口继承上一个练中创建的接口。这个接口应当具有一个附加操作,下表对这个操作进行了说明:

标识符 返回类型 参数

getBodyTemp Int() String

标识符

返回类型

参数

getBodyTemp

Int()

String

转自:http://www.cnntec.com

原文地址:https://www.cnblogs.com/drgraph/p/2364636.html