首页 > 代码库 > 类 (3) - 继承和多态

类 (3) - 继承和多态

继承用来描绘现实情境中的is-a关系,即某物属于某种类别。c#不支持多重继承,但可以通过接口实现多重继承。通过继承,子类可以扩充父类的内容。

多态指的是根据类型的不同,相同的请求(相同的方法)可以做出不同的相应。

C#实现多态最重要的方式就是接口。一个接口可能包括任意多个虚拟或者抽象方法,此时,继承了(实现了)该接口的类必须给出一个自己的实现(通过重写虚拟或者抽象方法)。例如基类拥有虚拟方法speak,其没有实现,此时所有的派生类都要提供一个自己的实现,然后,对于任意的派生类,speak都对应着不同的代码。我们在处理时仅需简单的调用X.speak(),编译器会根据调用对象X的类型,自动去寻找并执行不同的实现,这就是多态。

多态还可以通过子类复写父类的方法来实现。

重载,重写和隐藏

重载:同一个作用域内发生(比如一个类里面),定义一系列同名方法,但是方法的参数列表不同。这样才能通过传递不同的参数来决定到底调用哪一个。而返回值类型不同是不能构成重载的。

重写:继承时发生,在子类中重新定义父类中的方法,子类中的方法和父类的方法是一样的。例如: 基类方法声明为virtual(虚方法),派生类中使用override申明此方法的重写. 要注意:

  • 覆盖的方法的标志必须要和被覆盖的方法的名字和参数(输入和输出)完全匹配,才能达到覆盖的效果;
  • 覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
  • 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

隐藏:基类方法不做申明(默认为非虚方法),在派生类中使用new声明此方法的隐藏。它将隐藏任何在它之上(父类或祖先类)的实现。

overload(重载)——根据参数选择调用的方法;

override(重写)——访问父类子类,皆调用子类的重写方法,此时父类的方法必须要用virtual或abstract修饰否则无法重写;

new(隐藏)  ——访问父类则调用父类的方法,子类则调用子类的方法。

当子类和父类的方法名称和输入都完全一样且没有使用override关键字声明重写时,调用哪个方法取决于调用者是父类的实例,还是子类的实例。(这会造成编译器警告)

例如:

class A    {        public void F()        {            Console.WriteLine("A.F");        }    }    class B : A    {        public new void F() //写不写这个new都行,编译器自己加上这个new        {            Console.WriteLine("B.F");        }    }    class Test    {        static void Main(string[] args)        {            B b = new B();            b.F();            A a = b;            a.F();        }    }

输出为  
       B.F  
       A.F 

 

Virtual修饰词

Virtual方法也叫做虚方法,其本身可以有自己的实现。这个关键字用于需要被子类重写的父类方法。虚方法可以在子类被重写,但重写不是必须的。子类的被继承的方法使用OVERRIDE关键字声明重写。这个关键字和抽象对立,抽象方法不能有自己的实现,且必须在子类被重写。

 

Abstract修饰词

子类别与父类别(注一)之间的关系可以说是一种「Is A」的关系。假设有一个叫做车子的类别,另外有一个叫做卡车的类别。如果卡车是继承了车子类别而成为车子的子类别,我们可以就说卡车「Is A」车子,也就是说卡车是车子的一种。那么抽象类别呢(Abstract Class)?原则上最简单的解释是,抽象类别便是在领域概念中无法具体化的类别。这样讲似乎还是很笼统,我们再举一个简单的例子。假设有一个叫做「形状」的类别,而「圆形」类别与「方形」类别则是「形状」类别的子类别。在这个例子中,「形状」很明显的是一种抽像的概念,并无法具体化,因此在设计时,我们便可以选择将「形状」定义为一个抽象类。

抽象类中可以包含抽象方法,也可以有普通方法。抽象方法是一个仅有签名没有实现的方法,必须放在抽象类中。继承该类的子类如果是普通的类别,则要实现父类所有的抽象方法,如果也是抽象类,则只需要实现至少一个父类的抽象方法。我们不希望用户建立一个抽象类的实例,因为这毫无意义(既没有属性,方法又都是空的)。所以,我们有:

  1. 抽象类不能建立实例。
  2. 不能密封抽象类因为这违反了他只有被继承才有意义的原则。
  3. 抽象类只有被派生类继承才有意义。例如圆形和正方形派生了形状,其拥有属性半径,边长和方法计算周长,计算面积等。

 

接口

接口是一种特殊的抽象类,可以看成是一堆抽象方法的集合。所以他和抽象类型的共同点是:其包含若干抽象成员,且接口是抽象类型所以不能被实例化。

不同点则有:

  1. 接口内部只能包括抽象方法和属性,而抽象类型中可以包括非抽象方法(其还可以包括字段,构造函数等,这些接口都不能有)。
  2. 接口所有的成员都是公共的,抽象的。所以接口成员不指定访问修饰符。
  3. 继承接口的类只能全部实现接口的方法,不能部分实现。而继承抽象类型的类可以仅仅实现部分父类的方法。
  4. 接口也可以被接口继承(这点和抽象类相同),此时,子接口仍然不能实现父接口的方法(和抽象类不同),他只能提供额外的方法。如果出现孙类别,则孙类别要实现父接口和子接口上所有的方法。一个接口可以继承多个接口。

接口被当作为实现某种行为的容器。一个类可以继承不止一个接口,通过这种方式,我们不仅实现了多重继承,还做到了为类定制功能。对于一个类,只要他继承并实现了某个接口,就认为是实现了某种功能,类似他插上了一个真实世界的接口。接口负责刻画has-a关系,即某物所具有的功能,而不是强调归属感。这点和抽象类有所不同。面向对象思想和核心之一叫做多态性,什么叫多态性?说白了就是在某个粒度视图层面上对同类事物不加区别的对待而统一处理。而之所以敢这样做,就是因为有接口的存在(http://www.cnblogs.com/leoo2sk/archive/2008/04/10/1146447.html)。

 

接口和抽象类:接口和抽象类的一个区别在于,抽象类和它的子类之间应该是一般和特殊的关系,而接口仅仅是它的子类应该实现的一组规则( - T2噬菌体)。

例如c#中著名的接口IEnumerable,实现了它的所有类都可以被foreach枚举,但实现它的类和枚举本身没有关系。接口用来丰富类的功能,类似横向扩充,而抽象类则刻画纵向的“从一般到特殊”的关系。另外,如果我们希望让多于一个类继承某个类,则只能通过接口才能实现,例如让多个类都实现可被枚举的功能。

 

IEnumerable接口

该接口允许实现其的类进行foreach运算(迭代器)。实现该接口必须提供方法GetEnumerator的实现。GetEnumerator返回一个对另外一个接口IEnumerator的一个引用。这个接口里面有三个方法,他们真正实现了迭代器:

  • Movenext(),将光标向后移动一位
  • Current,读取当前值
  • Reset,将光标置于第一个成员之前

 我们有时候不需要将这3个方法全部实现,可以返回实现了IEnumerator的类,例如System.Array即可。

    public class Parent    {        public static void Main()        {            Garage g = new Garage();            foreach (Car c in g)            {                Console.WriteLine(c.name);            }            Console.ReadKey();        }    }    //car对象    public class Car    {        public string name { get; set; }        public Car(string n) { this.name = n; }    }    //garage类包含一组car对象    public class Garage : IEnumerable    {        public Car[] carArray = new Car[2];        public Garage()        {            carArray[0] = new Car("Benz");            carArray[1] = new Car("Honda");        }        public IEnumerator GetEnumerator()        {            //借助system.array的力量,其已经实现了IEnumerator,所以我们可以返回这个作为结果            return carArray.GetEnumerator();        }    }

 

利用yield关键字实现迭代器 

在c# 2.0之后,编译器提供了yield关键字,使得实现枚举变得更加容易。简单来说,yield(一般都在循环中)就是每次给消费他的方法吐出来一个值直到循环完毕,至于是怎么吐法可以自己决定。通过yield结合for循环,我们可以构建许多自定义的迭代器,比如下面若干例子:

替代传统方法的迭代器

        public IEnumerator GetEnumerator()        {            foreach(Car c in carArray)                yield return c;        }

逆序返回的迭代器

        public IEnumerator GetEnumerator()        {            for(int i=carArray.Length; i > 0 ; i--)                yield return carArray[i];        }

仅返回偶数项的迭代器

        public IEnumerator GetEnumerator()        {            for(int i=carArray.Length; i > 0 ; i--)                if (i % 2 == 0)                    //可以理解为:合乎要求,拿去吧!                    yield return carArray[i];                        }

 

ICloneable

实现其中的clone()方法,使得类可以深复制对象。(复制出来的新的对象和原对象不再指向堆上的同一实例,他们拥有相互独立的值)

 

IComparable与IComparer

实现其中的CompareTo()方法,实现对类的比较。值类型例如整数等,已经实现了该方法,所以我们可以比较2个整数的大小。

    public class Car : IComparable    {        //现在可以通过比较车名来比较两个车类        public string name { get; set; }        public Car(string n) { this.name = n; }        //实现CompareTo方法        int IComparable.CompareTo(object c)        {            Car temp = c as Car;            if (temp == null)                throw new ArgumentException("Not a car");            else            {                //借助字符串比较这个现成方法即可                return string.Compare(this.name, temp.name);            }        }    }

假如我们对一个类需要有多于一个基准的排序方法(例如名字,价格等),我们可以通过新建若干个方法并令它们实现IComparer接口。此时我们可以调用该类的数组的sort方法的重载,其第二个参数可以传入任意一个实现了IComparer接口的对象的实例,作为排序的基准。

 

    public class Car : IComparable    {        public string name { get; set; }        public int price { get;set; }    }    public class NameComparer : IComparer    {        int IComparer.Compare(Car o1, Car o2)        {            //借助字符串比较这个现成方法即可            return string.Compare(o1.name, o2.name);                    }    }    public class PriceComparer : IComparer    {        int IComparer.Compare(Car o1, Car o2)        {            if (o1.price > o2.price) return 1;            else if (o1.price == o2.price) return 0;            else return -1;        }    }    public class MainClass    {        public static void Main()        {            Car[] myCar = new Car[2];            myCar[0] = new Car            {                name = "Benz",                price = 100            };            myCar[1] = new Car            {                name = "Honda",                price = 80            };            Array.Sort(myCar, new NameComparer());            Array.Sort(myCar, new PriceComparer());        }    }

 


 

类 (3) - 继承和多态