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


    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

  • 相关阅读:
    C++输入与输出
    数组与指针
    MFC+WinPcap编写一个嗅探器之零(目录)
    netty源码分析之揭开reactor线程的面纱(二)
    netty源码分析之揭开reactor线程的面纱(一)
    Vert.x 线程模型揭秘
    理解 RxJava 的线程模型
    Java RESTful 框架的性能比较
    Java借助CountDownLatch完成异步回调
    在 Java 中运用动态挂载实现 Bug 的热修复
  • 原文地址:https://www.cnblogs.com/drgraph/p/2364636.html
Copyright © 2020-2023  润新知