C#基础系列——一场风花雪月的邂逅:接口和抽象类

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

C#基础系列目录:

一、业务场景介绍。

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

二、代码示例

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

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
    {
        [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、说明

这是一种比较典型的应用场景。接口定义规则,抽象类定义公共实现或者抽象方法,具体子类实现或者重写抽象类方法。我们重点来看这里的中间桥梁——抽象类。我们知道,抽象类里面既可以有实现的方法,也可以有未实现的抽象方法。

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

(2)另一方面,抽象类里面也定义了抽象方法,这个抽象方法在这里的作用就很好体现了:如果子类不重写父类的抽象方法,编译通不过,直接报错。这样就要求我们子类必须要重写抽象方法。从这点来说,抽象方法和接口的方法申明区别不大。

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

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

貌似也没问题,反正子类要重写的。确实,这样设计没问题,但是如果你不慎子类忘了override呢?程序还是会跑起来,运行的时候可能会报错。微软既然给我们提供了abstract这么一个东西,我们为什么不用呢。

三、代码扩展

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

比如我们现在又来了新的需求,中兴也要用我们的采集系统,但是它的设备类型、登录注销方式和华为设备区别非常大。那么这个时候我们接口的意义就体现了,如果我们使用接口,我们只需要再重写一个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、用一句话概括接口和抽象类的区别:使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性(依赖倒置)。至于使用的时候到底是用接口还是抽象类,看具体的情况。

时间: 2024-12-12 07:07:16

C#基础系列——一场风花雪月的邂逅:接口和抽象类的相关文章

夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接口与抽象类的本质区别是什么? 基本语法区别 设计思想区别 如何回答面试题:接口和抽象类的区别? 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl

Python基础----继承派生、组合、接口和抽象类

类的继承与派生 经典类和新式类 在python3中,所有类默认继承object,但凡是继承了object类的子类,以及该子类的子类,都称为新式类(在python3中所有的类都是新式类) 没有继承object类的子类成为经典类(在python2中,没有继承object的类,以及它的子类,都是经典类) 1 class People: 2 pass 3 class Animal: 4 pass 5 class Student(People,Animal): #People.Animal称为基类或父类,

Java基础系列12:使用CallableStatement接口调用数据库中的存储过程

前言:以下内容均以MySQL中的存储过程举例说明 一 存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它. 一个存储过程是一个可编程的函数,它在数据库中创建并保存.它可以有SQL语句和一些特殊的控制结构组成.当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是

对接口编程:接口和抽象类

在<大话设计模式>这本书中,我会听到这个字眼"对接口编程",而在文中的类图中,会提出这样一个疑问?为什么有的用的是:接口?有的用的是抽象类呢? 本文主要介绍以下内容:接口.抽象类. 文章的最后,会给出软考下午设计模式题中的一些做题技巧 ? 什么是接口? 接口是包含一组虚方法的抽象类型,其中每一种方法都有其名称.参数和返回值.接口方法不能包含任何实现,CLR 允许接口可以包含事件.属性.索引器.静态方法.静态字段.静态构造函数以及常数. 注意:一个类可以实现多个接口,当一个类

C#基础系列-接口

前言: 完全是我在学习过程中记录的笔记,只不过分享一下让很多刚开始学习.net编程的人能够很快的学会C#语言 接口 (1) 有时候需要考虑子类中一部分具有某些能力(方法),同时又需要考虑其多态的特征,这时候可以考虑接口 (2)语法: public interface 接口名 { //成员 } (3) 接口的存在就是为了被实现 (4)有了接口以后实现该接口的子类可以赋值给这个接口的变量,就可以实现多态了 (5)接口的存在就是为了多态 (6)接口与类的其中一个不同点在于多"继承"(实现)

C#基础系列:开发自己的窗体设计器(PropertyGrid显示中文属性名)

既然是一个窗体设计器,那就应该能够设置控件的属性,设置属性最好的当然是PropertyGrid了,我们仅仅需要使用一个PropertyGrid.SelectedObject = Control就可以搞定,让PropertyGrid显示Control的所有属性.可是这里显示的属性名是英文的.对于我们开发人员来说这无可厚非,我们也乐于接受.并且让PropertyGrid显示中文属性名,这对于我们开发人员的使用来说显得多此一举.可是,对于我这种类型的一个应用工具,英文属性名对于很多客户来说可能就很难懂

Spring基础系列12 -- Spring AOP AspectJ

Spring基础系列12 -- Spring AOP AspectJ 转载:http://www.cnblogs.com/leiOOlei/p/3613352.html 本文讲述使用AspectJ框架实现Spring AOP. 再重复一下Spring AOP中的三个概念, Advice:向程序内部注入的代码. Pointcut:注入Advice的位置,切入点,一般为某方法. Advisor:Advice和Pointcut的结合单元,以便将Advice和Pointcut分开实现灵活配置. Aspe

【C++自我精讲】基础系列六 PIMPL模式

0 前言 很实用的一种基础模式. 1 PIMPL解释 PIMPL(Private Implementation 或 Pointer to Implementation)是通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏. 2 PIMPL优点 举例: //x.h class X { public: void Fun(); private: int i; //add int i; }; //c.h #include <x.h> class C { public: void Fun()

J2EE开发实战基础系列之开卷有益

时隔七年再次接触培训有关的事情,是兴奋,更多的是恐惧,不知该如何下手. 本系列针对有Java语法基础的开发者或者爱好者,从工作开发角度出发讲解,不同于其他视频,一切皆以实用为主,过程中如有疑问,请提问于我,回答将发布在教程中添加提问部分,提问者越多,教程覆盖越全面,以实际问题为主. ----------------------------------------------------------------------------------------- 首先介绍下目前J2EE方面培训的入门