C#读书笔记之继承、接口与抽象类续


C#读书笔记之继承、接口与抽象类http://www.cnblogs.com/linjzong/articles/2717173.html

接下来说明最开始提出的问题:为什么要使用接口和抽象类?

刚接触接口和抽象类的概念的朋友大部分都会和我有一样的困惑吧:这两玩意到底有什么用,而且还这么像,有什么区别?

继续用两个例子说明分别说明接口和抽象类是干什么用的。

2.抽象类

还是我们的小商铺。我们知道,电器都有一个通电开关功能,但是,不同的电器通电之后产生的效果是不一样的,电冰箱是制冷,电灯是发亮,电视机是放节目。现在假设有三个类,

Fridge,Light,和Televison,他们都有两个方法PowerOn(),PowerOff():

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Blog1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
    class Television
    {
        //数量
        public int Count { get; set; }
        public void PowerOn()
        {
            Console.Write("电视机开了,正在放映节目。");
        }
        public void PowerOff()
        {
            Console.Write("电视机关闭,正在放映节目。");
        }
    }
    class Fridge
    {
        //数量
        public int Count { get; set; }
        public void PowerOn()
        {
            Console.Write("冰箱开了,里面好冷。");
        }
        public void PowerOff()
        {
            Console.Write("冰箱关了,温度回升。");
        }
    }
    class Light
    {
        //数量
        public int Count { get; set; }
        public void PowerOn()
        {
            Console.Write("电灯开了,房间变亮了。");
        }
        public void PowerOff()
        {
            Console.Write("电灯关了,房间又暗了。");
        }
    }
}

这个时候,当我们想要构建一个类似前面的Electronics类来实现继承时,我们会发现无从下手。虽然Fridge,Light,和Televison都有一个PowerOn和PowerOff方法,但它们内部的实现是不同的。该怎么办呢?这就是抽象类大显身手的地方了。我们定义一个抽象的Electronics类,修改后的部分代码如下:

    abstract class Electronics
    {
        public int Count { get; set; }
        public abstract void PowerOn();
        public abstract void PowerOff();
    }

    class Light:Electronics
    {
        public override void PowerOn()
        {
            Console.Write("电灯开了,房间变亮了。");
        }
        public override void PowerOff()
        {
            Console.Write("电灯关了,房间又暗了。");
        }
    }
    class Fridge : Electronics
    {
        public override void PowerOn()
        {
            Console.Write("冰箱开了,里面好冷。");
        }
        public override  void PowerOff()
        {
            Console.Write("冰箱关了,温度回升。");
        }
    }
    class Television
    {
        //数量
        public int Count { get; set; }
        public void PowerOn()
        {
            Console.Write("电视机开了,正在放映节目。");
        }
        public void PowerOff()
        {
            Console.Write("电视机关闭,正在放映节目。");
        }
    }                                                                    

来看看合前面继承的区别:

1.定义Electronics类时使用了abstract关键字,且类内PowerOn和PowerOff也有abstract关键字,并且没有指定要做什么的代码。

2.继承于Electronics的Fridge类和Light类都写了PowerOn,PowerOff方法具体的实现,且加了override关键字。

这是抽象类和普通类的区别之处,abstract关键字表面了这个类是一个抽象的类。何为抽象?其实“家电”本身就是一个抽象概念,假如有顾客说:请给我一个‘家电’。商铺自然没法给出‘家电’这么一个东西,因为家电是一个抽象概念,表示的是包括电视机,电冰箱,电灯,空调等等一类物品的统称。而抽象方法,可以理解为不指名具体实现的方法,它只能在抽象类中定义,定义的方式就如例子中的PowerOn何PowerOff,它只是指出了这个抽象类有一个功能,但是这个功能的具体作用,必须由继承了这个抽象类中的派生类实现。

现在来思考一下使用抽象类有什么用:看了上面的代码,可能有些人会问,既然抽象类的实现必须在其子类中重新写,那我又何必定义它呢,如果有20种电器,我还是得写20种PowerOn和PowerOff,根本没有减少我的代码量。在这个例子中是的,但是别忘了我们前面说过的继承的作用:虽然抽象类的具体实现必须得在子类中重写,但是我们起码拥有了一个统一的类的格式。我们知道了继承于这个抽象类的类都有而且必须有一个PowerOn和PowerOff方法,这是十分重要的。

假如现在商铺需要完成另外一个功能,它需要给顾客展示各种商品通电后的作用。我们可以写这么一个方法

class Shop
    {
        public void Show(Electronics ele)
        {
            ele.PowerOn();
            ele.PowerOff();
        }
    }

在这个方法中,我们传入了一个抽象类Electronics类型的对象ele,并且调用了它的PowerOn方法和PowerOff方法,利用前面的知识我们知道,我们同样可以传入Fridge和Light类型的对象,因为它们继承于Electronics,而且在这里会调用Fridge和Light类型对象内部已经实现的PowerOn和PowerOff方法。

当然,我们不能忘了抽象类和普通类的区别,比如说它同样有Count属性,因为Count不是抽象的,所以子类可以直接继承。

接下去说接口:

3.接口

接口是一个比抽象类更抽象的概念。粗看之下它有点类似于只定义了抽象方法的一个抽象类,但是,本质上它们是有区别的。看下面的例子。

还是这家商铺,这家商铺里有电视机,收音机,电灯必需品,然后主人还养了一只鹦鹉,这只鹦鹉会唱歌。我们知道,电视机是能发光还能发出声音的,而收音机只能发出声音,电灯只能发光,这只会唱歌的鹦鹉也能发出声音。现在,这个商铺主人有这么两个需求:当天黑了之后,他需要一点亮光,这个时候他可以选择打开灯或者电视机,当他无聊的时候,他喜欢打开电视机听歌,或者打开收音机听歌,或者听这只鹦鹉唱歌。我们把这两个需求定义为NeedLight()和NeedSong(),如下:

class Master
    {
        public void NeedLight(** lighter)
        {
            //提供光
        }
        public void NeedSong(** singer)
        {
            //唱歌
        }
    }

在这里,我们需要提供一个“发光体”和一个“歌唱者”的实例进去,调用它们的方法。思考一下,我们该传个什么类型进去?

在正式介绍接口前,我们先提供几个可行方案,并一一指出它们的缺陷。

首先是最简单又最麻烦的一种。

  class Televison
    {
        public void Shine()
        {
            Console.WriteLine("电视机发光");
        }
        public void Sing()
        {
            Console.WriteLine("播放唱歌节目");
        }
    }
    class Radio
    {
        public void Sing()
        {
            Console.WriteLine("收听唱歌电台");
        }
    }
    class Light
    {
        public void Shine()
        {
            Console.WriteLine("电灯亮了");
        }
    }
    class Parrot
    {
        public void Sing()
        {
            Console.WriteLine("鹦鹉在唱歌");
        }
    }

我们给四个类都定义了各自的唱歌(sing)和发光(Shine)方法,在Master类里并可以这么调用:

 class Master
    {
        public void NeedTelLight(Televison lighter)
        {
            lighter.Shine();
        }
        public void NeedLitLight(Light lighter)
        {
            lighter.Shine();
        }
        public void NeedTelSong(Televison singer)
        {
            singer.Sing();   
        }
        public void NeedRadSong(Radio singer)
        {
            singer.Sing();
        }
        public void NeedParSong(Parrot singer)
        {
            singer.Sing();
        }
    }

学了继承之后,我们自然不会满足于这种方法,它的缺点太明显了,代码重复,可扩展性也差。所以,我们可以尝试第二种方法,利用一个抽象类:

   abstract class SingerAndLighter
    {
        public abstract void Shine();
        public abstract void Sing();
    }
     class Master
    {
         public void NeedLight(SingerAndLighter lighter)
        {
            lighter.Shine();
        }
         public void NeedSong(SingerAndLighter singer)
        {
            singer.Sing();
        }
     }

让电视机,收音机,电灯和鹦鹉继承了这个抽象类后,我们并可以传入一个SingerAndLighter类型的参数到NeedLight和NeedSong方法里。但是等等,这种设定未免有些荒谬,因为这就意味着我们有了一个会唱歌的电灯和一只会发光的鹦鹉。这明显是不符合实际情况的。

那么,下面这种方法又如何呢。

   abstract class Singer
    {
        public abstract void Sing();
    }
    abstract class Lighter
    {
        public abstract void Shine();
    }
     class Master
    {
         public void NeedLight(Lighter lighter)
        {
            lighter.Shine();
        }
         public void NeedSong(Singer singer)
        {
            singer.Sing();
        }
    }

我们定义了一个能唱歌的类Singer和一个能发光的类Lighter,并且让NeedLight方法接受一个Linghter类型的对象,NeedSong方法接受一个Singer类型的对象。这样它们各司其责,似乎可以解决问题了。可是,别忘了,我们还有一个特殊的存在:电视机,它能同时实现发光和唱歌的功能。这是个麻烦,因为类是没法同时继承多个基类的。除非我们继续引用前面定义好的那个SingerAndLighter类,让电视机继承这个类,但这又意味着我们需要在Master类里添加两个新方法来处理这个SingerAndLighter类型的参数。

其实我们离答案已经很近了。在一开始,我们在处理的方向上出了一点问题。再来看看我们需要定义的四个类:电视机,收音机,电灯和一只会唱歌的鹦鹉。我们一直在寻找一种方法,能够将它们归类成一个抽象概念,就像电视机,电冰箱之于家电,但这是错误的,因为我们需要的不是它们本身的共同点,而是它们能做的事情的,即方法的共同点。

这个共同点我们把它称为接口。

何为接口?以一个通俗的概念来说,接口表示的就是一个“可以做的事情”的集合。电视机能发光,能唱歌,这就是它“可以做的事情”,在这里,我们需要做的就是把它们可以做的事情提取出来,形成接口。看如下代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Blog1
{
    class Program
    {
        static void Main(string[] args)
        {
            Televison tel = new Televison();
            Radio rad = new Radio();
            Light lig = new Light();
            Parrot par = new Parrot();
            Master LinJ = new Master();
            LinJ.NeedLight(tel);
            LinJ.NeedLight(lig);
            LinJ.NeedSong(tel);
            LinJ.NeedSong(rad);
            LinJ.NeedSong(par);
            Console.ReadLine();
        }
    }
    class Televison:ISinger,ILighter
    {
        public void Shine()
        {
            Console.WriteLine("电视机发光");
        }
        public void Sing()
        {
            Console.WriteLine("播放唱歌节目");
        }
    }
    class Radio : ISinger
    {
        public void Sing()
        {
            Console.WriteLine("收听唱歌电台");
        }
    }
    class Light : ILighter
    {
        public void Shine()
        {
            Console.WriteLine("电灯亮了");
        }
    }
    class Parrot : ISinger
    {
        public void Sing()
        {
            Console.WriteLine("鹦鹉在唱歌");
        }
    }
    interface ISinger
    {
         void Sing();
    }
    interface ILighter
    {
         void Shine();
    }
     class Master
    {
         public void NeedLight(ILighter lighter)
        {
            lighter.Shine();
        }
         public void NeedSong(ISinger singer)
        {
            singer.Sing();
        }
    }
}

在这里,我们先定义了两个接口ISinger和ILigter,分别实现了Sing方法和Shine方法,然后让电视机,收音机,电灯和鹦鹉继承了相应的接口,并且实现了具体的方法。和用抽象类相比,这里的电视机同时继承了两个接口,这是接口允许的。

4.总结

通过上面的例子可以很清楚地说明了为什么要使用继承,抽象类,接口,接下去我要引用一段网上看到的话来说明它们之间的关系。

铁门木门都是门(抽象类),你想要个门我给不了(不能实例化),但我可以给你个具体的铁门或木门(多态);而且只能是门,你不能说它是窗(单继承);一个门可以有锁(接口)也可以有门铃(多实现)。门(抽象类)定义了你是什么,接口(锁)规定了你能做什么(一个接口最好只能做一件事,你不能要求锁也能发出声音吧(接口污染))。

这段话里的很多概念这里没讲到,不过不难理解。其中“门(抽象类)定义了你是什么,接口(锁)规定了你能做什么”这句话阐述了接口和抽象类最本质的区别。抽象类实现了 “is-a”关系,“电冰箱 is a 家电”。接口实现了“can do”关系,“鹦鹉 can 唱歌”。这便是它们最本质的区别。尽管有时候我们觉得使用抽象类和使用接口可以完成同样的功能,但是仔细想想我们要定义的类之间的关系,便能明白究竟哪一种才是合适的选择。

优质内容筛选与推荐>>
1、将表里的数据批量生成INSERT语句的存储过程 增强版
2、【转】NET Core实战项目之CMS-004 Dapper的快速入门
3、从原则、方案、策略及难点阐述分库分表
4、Webpack
5、并发修改异常处理


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn