接口和抽象类的区别,你真的了解吗

老胡最近浏览博客,发现很多公司的面试题库上面,都有一道比较经典的题目,描述C#中接口和抽象类的区别。不由的想起,几年前我去面试的时候,也是被这么问过。似乎这是一道简单的不能再简单,寻常的不能再寻常的题目。可是,你真的了解这两者的区别吗?今天就让我们来聊聊这个话题吧。
 

首先提纲挈领地总述一下,要想回答好这个问题,能够在一个比较有水平的面试官手上拿到不错的分数,起码我们应该从语法,语意和用途这三个层次来思考它们。

语法层面

抽象类
  • 抽象类是类,C#不允许多继承,所以一个类只能有至多一个抽象类基类
  • 抽象类不能被实例化
  • 抽象类自身可以包含一部分实现代码
  • 抽象类子类必须实现抽象方法
	abstract class Sharp
    {
        public abstract void Draw();
        public void BeforeDraw() { }
        public void AfterDraw() { }
    }

    class Circle : Sharp
    {
        public override void Draw()
        {
            Console.WriteLine("Draw Circle");
        }
    }

    class Triangle : Sharp
    {
        public override void Draw()
        {
            Console.WriteLine("Draw Triangle");
        }
    }

上面就是一个例子,Sharp是一个抽象类,有抽象方法Draw和非抽象方法BeforeDraw和AfterDraw。作为子类,Triangle和Circle被要求实现抽象方法Draw。

接口
  • 接口是一系列方法实现的承诺,类可以有多个接口
  • 接口中的方法不允许有访问修饰符,默认为public
  • 接口中不能包含实现代码

如果用接口来写刚刚的例子,那么应该是

	public interface IDrawable
    {
         void Draw();
    }

    class Circle : IDrawable
    {
        public void Draw()
        {
            Console.WriteLine("Draw Circle");
        }
    }

    class Triangle : IDrawable
    {
        public void Draw()
        {
            Console.WriteLine("Draw Triangle");
        }
    }

这就是接口和抽象类的区别中的语法部分,这是一个面试者在面试的时候最起码应该回答出来的部分,如果连这个也回答不出,那么这道题就算糊了,毕竟,这是最最基础的部分。但是如果面试官再问一句,“为什么他们会这么设计”,可能有些人就不知道该如何回答了。这个时候,面试官问的是你对这他们的语意的理解。

语意层面

语法是为语意服务的,语意的知识比起语法,更加接近知识的源头。就像水源一样,越接近源头越清澈见底。如果能了解接口和抽象类之于语意层面的区别,我们对这块知识的掌握会更上一个台阶。
 

抽象类

在OOP的世界中,如果我们把类实例比作一个产品,那么类就是产品的模板,抽象类就是一个模板半成品。抽象类中具体的代码,就是模板半成的那部分,缺失的抽象方法,就是留给子类以填充的另一部分模板。只有当子类把模板填充完毕之后,这个模板才能投入使用生产产品(即,生成具体的类实例),这也可以解释为什么不能创建模板类的实例。
 
在刚刚的代码中,

   abstract class Sharp
   {
       public abstract void Draw(); //抽象的部分,允许子类自定义完成类的构建,以此方式完成多态
       public void BeforeDraw() { } //具体的部分
       public void AfterDraw() { } //具体的部分
   }

抽象类以子类继承的方式,完成多态
 

接口

不同于抽象类,接口更像是产品规格。类通过实现接口以表明其有能力履行接口声明。跟现实生活中一样,一个模板可以具有多方面的产品规格,所以一个类完全可以实现多个接口。或者可以这么想,在OOP的世界中,接口代表了类的社会角色,框架和其他类使用者通常不需要关注具体使用的类,只需要关注接口。比如,在某个函数中,我们需要一个IDrawable对象,那么我们通常不关心我们具体使用的是Circle还是Triangle。
在刚刚的例子中,抽象类Sharp完全可以声明其实现了IDrawable接口。

abstract class Sharp : IDrawable //完全合法
{
}

类通过实现不同接口完成另一种形式的多态,即,在不同的上下文中,类以不同的面貌出现。实现接口表明类具有接口声明的能力,更方便类被使用

如果能回答出使用抽象类主要为了通过定制化子类完成多态,接口的目的是为了更好地被用,那么这道题就算答得不错了,接下来如果能再补充一下通常用途,就更好了。
 

用途

抽象类
  • 当父类想要定制化某些操作但是又想开放一部分操作让子类覆盖的时候
  • 当某些概念确实很抽象,没有被实例化的需要的时候,比如上面的Sharp。谁能知道一个抽象概念如何被实例化?
  • 当使用一些设计模式的时候,比如模板方法。上面的Sharp稍微修改一下,就是现成的模板方法模式。没有抽象类,实现模板方法应该是非常困难的
接口
  • 当做单元测试,需要MOCK的时候,MOCK一般和接口配合的很好
  • 当类想要在不同的语境下被使用的时候,毕竟,很多时候函数声明只会声明需要某种接口而不是某个特定类。这样同一个类就可能在不同的语境下以不同的接口实现者身份出现。切记,接口的作用是让类更好的被使用。而且我们类的使用者不限于其他程序员,.NET框架也会调用我们的类,比如,当我们声明类实现ISerializable接口,.NET框架就知道这个类可以被序列化。
  • 在某种情况下,可以当做标记接口。有些接口里面一个方法也没有。这些接口就叫做标记接口,他们的作用是标记类具有某些身份。不过,这种使用方法现在使用比较少,多建议使用属性来替代标记接口。

怎么样,看来这篇文章,是不是对抽象类和接口的区别有更深层次的了解了?老胡希望下次面试的时候遇到这种问题,大家能够答的很漂亮哦!

猜你喜欢

转载自blog.csdn.net/deatharthas/article/details/106558498