美文网首页
【原创】一场风花雪月的邂逅:接口和抽象类

【原创】一场风花雪月的邂逅:接口和抽象类

作者: 极乐君 | 来源:发表于2016-11-28 18:19 被阅读0次

    来源:一场风花雪月的邂逅:接口和抽象类

    前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的。那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗。博主呵呵一笑,回想当初的自己,不也有此种疑惑么!所以今天打算针对他的问题,结合一个实际的使用场景来分享下抽象类和接口的异同,到底哪些情况需要用接口?又有哪些情况需要用抽象类呢?

    一、业务场景介绍

    博主打算使用原来在华为做外包的时候一个场景:我们针对华为里面的设备做了一个采集设备使用率的程序,设备的类型很多,各种设备的登录和注销方式基本相同,但是每种设备的采集的规则又不太相同。大致的场景就这样,我们来看代码吧。

    二、代码示例

    根据业务场景,我们简单搭建代码,先来看看代码结构图:


    代码结构图代码结构图

    ESTM.Spider:项目的入口程序,只为测试,这里就简单用了一个控制台程序。
    ESTM.Spider.Huawei:华为设备采集规则,定义接口抽象实现和具体实现。
    ESTM.Utility:解决方案的工具类和接口。

    下面来看看具体的实现代码:

    1、工具类

    namespace ESTM.Utility
    {
        public class LoginUser
        {
            public string Username { set; get; }
    
            public string Password { set; get; }
        }
    
        public class Device
        {
            public string DeviceType { set; get; }
    
            public int WaitSecond { set; get; }
        }
    }
    

    2、接口设计:ISpider.cs

    namespace ESTM.Utility
    {
        //采集接口,定义采集的规则
        public interface ISpider
        {
            bool Login(LoginUser oLoginUser);
    
            string Spider(Device oDevice);
    
            void LoginOut();
        }
    }
    

    3、接口抽象实现类:SpiderBase.cs

      /// <summary>
        /// 公共的采集基类
        /// </summary>
        public abstract class SpiderBase : ISpider
        {
            //华为设备统一采用Telnet方式登录。统一用户名密码都是admin。
            public virtual bool Login(LoginUser oLoginUser)
            {
                Console.WriteLine("华为设备采用Telnet方式登录。");
    
                var bRes = false;
                if (oLoginUser.Username == "admin" && oLoginUser.Password == "admin")
                {
                    Console.WriteLine("用户名密码校验正确,登录成功");
                    bRes = true;
                }
                else
                {
                    Console.WriteLine("用户名密码校验错误,登录失败");
                }
                return bRes;
               
            }
    
    
            //采集操作和具体的设备类型相关,这里用抽象方法,要求子类必须重写
            public abstract string Spider(Device oDevice);
            
    
            //华为设备统一注销
            public virtual void LoginOut()
            {
                Console.WriteLine("华为设备采用Telnet方式注销");
            }
        }
    

    4、接口具体实现类

      [Export("MML", typeof(ISpider))]
        public class SpiderMML:SpiderBase
        {
            //MML设备采集
            public override string Spider(Device oDevice)
            {
                Console.WriteLine("MML设备开始采集");
                return "MML";
            }
        }
    
        [Export("TL2", typeof(ISpider))]
        public class SpiderTL2:SpiderBase
        {
            //TL2设备采集
            public override string Spider(Device oDevice)
            {
                Console.WriteLine("TL2设备开始采集");
                return "TL2";
            }
        }
    

    5、在控制台调用

     class Program
        {
            //通过依赖注入来注入具体的实现类对象(SpiderMML对象)
            [Import("MML", typeof(ISpider))]
            public ISpider spider { set; get; }
    
            static void Main(string[] args)
            {
                var oProgram = new Program();
                RegisterMEF(oProgram);
    
                oProgram.spider.Login(new LoginUser() { Username = "admin", Password = "admin" });
                oProgram.spider.Spider(new Device() { DeviceType = "HuaweiDevice", WaitSecond = 100 });
                oProgram.spider.LoginOut();
            }
    
            #region 注册MEF
            private static void RegisterMEF(object obj)
            {
                AggregateCatalog aggregateCatalog = new AggregateCatalog();
                var thisAssembly = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
                aggregateCatalog.Catalogs.Add(thisAssembly);
                var _container = new CompositionContainer(aggregateCatalog, true);
                _container.ComposeParts(obj);
            } 
            #endregion
        }
    

    6、说明
    这里用到MEF的知识,不懂MEF没关系,你暂时把它当作一种依赖注入的容器来看吧!这并不影响你对本文的理解!

    这是一种比较典型的应用场景。接口定义规则,抽象类定义公共实现或者抽象方法,具体子类实现或者重写抽象类方法。我们重点来看这里的中间桥梁——抽象类。我们知道,抽象类里面既可以有实现的方法,也可以有未实现的抽象方法。那么这里为什么要有一个中间的抽象类?又为什么不能用一个具体的实现类?请听博主细细道来。

    (1)在这里,Login、LoginOut方法由于子类是通用的具有相同逻辑的方法,所以我们需要在抽象类里面去实现这两个方法,如果子类没有特殊需求,调用的时候直接用父类的方法就好了; 如果子类有特殊需求,可以override父类的方法。这样设计既提高了代码的复用率,也可以灵活复写。从这点来说,这里必须要有一个可以实现方法的类,至于是不是非抽象类不可,我们下面来介绍

    (2)如果这里不用抽象类,就用一个普通的类来代替行不行?博主的答案是:行!但不好!如果你非要说,我用一个普通的类,将public abstract string Spider(Device oDevice);这个方法写成

    public virtual string Spider(Device oDevice)
    {
          return "";  
    }
    

    貌似也没问题,反正子类要重写的。确实,这样设计没问题,但是如果你不慎子类忘了override呢?编译可以通过,程序还是会跑起来,运行的时候可能会报错。而使用抽象类,在这里抽象类能够约束子类必须要重写这个方法,如果忘了重写,编译直接不通过,这样看来,这里使用抽象类是不是更好呢~~

    (3)综合上述(1)和(2)来看,这里使用抽象类的好处就很明显了,一方面抽象类可以有实现类,这是接口不能代替的;另一方面,抽象类可以有抽象方法,约束子类必须重写,这是普通的类不能代替的。在一定程度上,可以说抽象类具备了接口和普通类的双重功能。经过这样一分析,你有没有理解抽象类的作用呢!

    三、代码扩展

    以上我们抽象类使用的必要性和使用方法是介绍完了。那么接下来新的问题来了,可能就有人问了,你上面说了叭叭叭说了这么多,无非就是说了抽象类的必要性,那么既然抽象类这么有用,我们直接用抽象类就好了,你干嘛还要弄一个接口呢。谈到这里,就要说到面向接口编程。其实,面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。那么这里是否可以不要接口,直接用抽象类代替呢?答案还是行!但不好!

    比如我们现在又来了新的需求,中兴也要用我们的采集系统,但是它的设备类型、登录注销方式和华为设备区别非常大。那么这个时候我们接口的意义就体现了,如果我们使用接口,我们只需要再重写一个类似ESTM.Spider.Huawei这样的项目就好了,我们暂且命名叫ESTM.Spider.Zhongxing。我们来看看:


    代码如下:

    namespace ESTM.Spider.Zhongxing
    {
        /// <summary>
        /// 中兴设备采集基类
        /// </summary>
        public abstract class SpiderBase:ISpider
        {
            //中兴设备通用登录方法
            public virtual bool Login(LoginUser oLoginUser)
            {
                Console.WriteLine("中兴设备登录前多了一个数据校验:.......");
                Console.WriteLine("中兴设备采用WMI方式登录。");
    
                var bRes = false;
                if (oLoginUser.Username == "root" && oLoginUser.Password == "root")
                {
                    Console.WriteLine("用户名密码校验正确,登录成功");
                    bRes = true;
                }
                else
                {
                    Console.WriteLine("用户名密码校验错误,登录失败");
                }
                return bRes;
            }
    
            //定义抽象方法,要求子类必须重写
            public abstract string Spider(Device oDevice);
    
            //中兴设备通用注销
            public virtual void LoginOut()
            {
                Console.WriteLine("中兴设备采用WMI方式注销");
            }
        }
    }
    
    namespace ESTM.Spider.Zhongxing
    {
        [Export("ZXGC", typeof(ISpider))]
        public class SpiderZXGC:SpiderBase
        {
            public override string Spider(Utility.Device oDevice)
            {
                Console.WriteLine("中兴ZXGC设备开始采集");
                return "ZXGC";
            }
        }
    }
    
    namespace ESTM.Spider.Zhongxing
    {
        [Export("ZXGY", typeof(ISpider))]
        public class SpiderZXGY:SpiderBase
        {
            public override string Spider(Utility.Device oDevice)
            {
                Console.WriteLine("中兴ZXGY设备开始采集");
                return "ZXGY";
            }
        }
    }
    

    由于这里采用了接口,我们将ESTM.Spider.Zhongxing这个项目开发完成后生成dll,将dll放到控制台程序中,直接通过MEF导入不同的子类对象就可以使用,不需要更改控制台里面的大部分东西。如果不用接口,而是直接用抽象类代替,那么控制台里面大部分的代码都得改,并且控制台程序依赖多个dll,对设计的松耦合也不利。博主这里为了简单,用了MEF来简单导入,其实正式项目中,应该是用工厂采用反射直接创建出具体的实例。

    从这点上来说,接口拥有抽象类不具备的特性:定义规则,实现设计的松耦合。

    四、总结

    1、接口是一组规则的集合,它主要定义的是事物的规则,体现了是这种类型,你就必须有这些规则的概念。它的目的主要是依赖倒置和松耦合,从这点来说,接口不能省掉或者用抽象类代替。总而言之,接口和抽象类不可同日而语。

    2、抽象类主要用于公共实现和约束子类必须重写。以上面的例子说明,Login、Loginout用于公共实现,提高了代码复用,Spider用于抽象,约束子类必须要重写Spider方法。这也就是这里不能用普通类的原因。

    3、用一句话概括接口和抽象类的区别:使用抽象类的动机是为了代码的复用,而使用接口的动机是为了实现多态性(依赖倒置)。至于使用的时候到底是用接口还是抽象类,看具体的情况。

    本文完!

    相关文章

      网友评论

          本文标题:【原创】一场风花雪月的邂逅:接口和抽象类

          本文链接:https://www.haomeiwen.com/subject/ceiopttx.html