接口

关于“接口”一词,跟我们平常看到的电脑的硬件“接口”意义上是差不多的。拿一台电脑来说,我们从外面,可以看到他的 USB 接口, COM 接口等,那么这些接口的目的一就是让第三方厂商生产的外设都有相同的标准,也是提供一个对外通信或操作的入口。

只是 C# 的接口除了以上特点之外,还具有一种类似于模板的功能,我们定义一组接口,就像是一个模板。这点和抽象类不同,抽象类是先有子类或都子类的概念,从中抽象出一个类。而接口更像是我们要设计一台机器,先把这台机器对外的功能接口列出来,然后再做具体的实现,当然这个说法是站在软件设计的角度来讲的。

1.什么是接口?

接口就是把隐式公共方法和属性组合起来,以封装特定功能的一个集合。

接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致。

接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接口定义中指定的接口成员。

接口使用 interface 关键字进行定义 ,可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。

接口的名称,通常使用 I 开头(按规范命名很重要)

2、接口的特性

A. 接口类似于抽象基类,不能直接实例化接口;接口中的方法都是抽象方法,实现接口的任何非抽象类型都必须实现接口的所有成员:

当显式实现该接口的成员时,实现的成员不能通过类实例访问,只能通过接口实例访问。

当隐式实现该接口的成员时,实现的成员可以通过类实例访问,也可以通过接口实例访问,但是实现的成员必须是公有的。

B. 接口不能包含常量、字段、运算符、实例构造函数、析构函数或类型、不能包含静态成员。

C. 接口成员是自动公开的,且不能包含任何访问修饰符。

D. 接口自身可从多个接口继承,类和结构可继承多个接口,但接口不能继承类。

3.接口和抽象类的区别 :

接口用于规范,抽象类用于共性。

接口中只能声明方法,属性,事件,索引器。而抽象类中可以有方法的实现,也可以定义非静态的类变量。

抽象类是类,所以只能被单继承,但是接口却可以一次实现多个。

抽象类可以提供某些方法的部分实现,接口不可以。

抽象类的实例是它的子类给出的。接口的实例是实现接口的类给出的。

在抽象类中加入一个方法,那么它的子类就同时有了这个方法。而在接口中加入新的方法,那么实现它的类就要重新编写(这就是为什么说接口是一个类的规范了)。

接口成员被定义为公共的,但抽象类的成员也可以是私有的、受保护的、内部的或受保护的内部成员(其中受保护的内部成员只能在应用程序的代码或派生类中访问)。

此外接口不能包含字段、构造函数、析构函数、静态成员或常量。

抽象类可以包含一些成员的实现,接口却不包含成员的实现;抽象类的抽象成员可以被子类实现,而接口的的成员需要实现类完全实现;一个类可能继承一个抽象类,但是可以实现多个接口。

4.接口和抽象类在设计角度的异同

上面说了接口和抽象类在表象上或说是形态上的区别,此外接口和抽象类在设计过程中也有很多不同之处,这也就是 C# 为何设计了抽象类还要设计接口的原因。即他们使用场合的不同。

第一、类是对对象的抽象;抽象类是对类的抽象;接口是对行为的抽象;

第二、如果行为跨越不同类的对象,可使用接口;对于一些相似的对象使用继承抽象类;

第三、从设计角度讲,抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类;而接口跟本不知道子类的存在,方法如何实现还不确认,即预先定义。

在实际项目中,抽象类往往是通过重构产生,除非你事先知道某类有很多子类要实现,从而抽象出抽象类,即自底而上设计。接口则是在项目设计之初就确实要实现那些行为,属于自顶向下设计。

5.代码示例

通过上面的学习,我们知道了抽象类和接口的区别以及他们在设计角度的使用。下面,我们按国际惯例重新设计我们的动物类( A nimal)以及他们的子类狗( D og)、猫(Cat)、羊( S heep)假如我们事先知道有一种羊“喜羊羊”( PleasantSheep )和一种猫“蓝猫”( B lueCat)他们有一个共同的行为就是会说话,这时候,我们肯定会想到给他们的抽象类 A nimal定义一个抽象方法说话(Speak),哈哈,傻了吧,犯错了吧,动物都可以讲话么?为了解决这种事例,我们需要用到接口,我们事先知道有一个“讲话”这个行为,那么我就定义一个讲话的接口(ISpeak).UML 图如下

下面看一下代码:

/// <summary>
/// 动物类(父类 抽象类)
/// </summary>
abstract class Animal
{
    /// <summary>
    /// 名字
    /// 说明:类和子类可访问
    /// </summary>
    protected string name;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="name"></param>
    public Animal(string name)
    {
        this.name = name;
    }

    private int shoutNum = 3;
    public int ShoutNum
    {
        get { return shoutNum; }
        set { shoutNum = value; }
    }

    /// <summary>
    /// 名字(虚属性)
    /// </summary>
    public virtual string MyName
    {
        get { return this.name; }
    }

    /// <summary>
    /// 叫声,这个方法去掉虚方法,把循环写在这里
    /// </summary>
    public void Shout()
    {
        string result = "";
        for (int i = 0; i < ShoutNum; i++)
            result += getShoutSound() + "!";

        Console.WriteLine(MyName);
        Console.WriteLine(result);
    }

    /// <summary>
    /// 创建一个叫声的虚方法,子类重写
    /// </summary>
    /// <returns></returns>
    public virtual string getShoutSound()
    {
        return "";
    }
}

/// <summary>
/// 声明一个接口 ISpeak(讲话)
/// </summary>
interface ISpeak
{
    void Speak();
}

/// <summary>
/// 狗(子类)
/// </summary>
class Dog : Animal
{
    string myName;
    public Dog(string name): base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:狗狗,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "汪!";
    }
}


/// <summary>
/// 猫(子类)
/// </summary>
class Cat : Animal
{
    string myName;
    public Cat(string name): base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:猫咪,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "喵!";
    }
}

/// <summary>
/// 蓝猫(子类)
/// 继承 Cat和接口ISpeak
/// </summary>
class BlueCat : Cat,ISpeak
{
    string myName;
    public BlueCat(string name) : base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:蓝猫,我叫:" + this.name; }
    }

    /// <summary>
    /// 实现接口ISpeak的成员Speak
    /// </summary>
    public void Speak()
    {
        Console.WriteLine("我会说人话:“你好,我叫:" + this.name + "~~~”");
    }
}


/// <summary>
/// 羊(子类)
/// </summary>
class Sheep : Animal
{
    string myName;
    public Sheep(string name): base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:羊羊,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "咩!";
    }
}

/// <summary>
/// 喜羊羊(子类)
/// 继承 Sheep和接口ISpeak
/// </summary>
class PleasantSheep : Sheep, ISpeak
{
    string myName;
    public PleasantSheep(string name) : base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:喜羊羊,我叫:" + this.name; }
    }
    /// <summary>
    /// 实现接口ISpeak的成员Speak
    /// </summary>
    public void Speak()
    {
        Console.WriteLine("我会说人话:“你好,我叫:" + this.name + "~~~”");
    }
}

调用一:

//喜羊羊来了出场了
Animal pleasantSheep = new PleasantSheep("最帅喜羊羊");
pleasantSheep.Shout();
ISpeak IPleasantSheepSpeak= new PleasantSheep("最帅喜羊羊");
IPleasantSheepSpeak.Speak();
Console.ReadLine();
//结果如下:
//我是:喜羊羊,我叫:最帅喜羊羊
//咩!!咩!!咩!!
//我会说人话:“你好,我叫:最帅喜羊羊~~~”

调用二:

//蓝猫出场了
Animal blueCat = new BlueCat("最美蓝猫");
blueCat.Shout();
ISpeak IBlueCatSpeak = new PleasantSheep("最美蓝猫");
IBlueCatSpeak.Speak();
Console.ReadLine();
//结果如下:
//我是:蓝猫,我叫:最美蓝猫
//喵!!喵!!喵!!
//我会说人话:“你好,我叫:最美蓝猫~~~”

6. 要点:

A.接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致

B.接口的类可以实现方法、属性、事件、索引器这四种类型,不能实现委托。后面要说,委托是一种类型,而接口成员非类型。

C.接口中的成员用来定义对象之间通信的契约,指定接口中的成员为私有或保护没有意义。它们默认为公有。

D.接口可以继承多个接口,但不能继承类

E.类可以继承多个接口,但不能继

F. 接口和类有什么异同 :

异:

不能直接实例化接口。

接口不包含方法的实现。

接口可以实现多继承,而类只能是单继承。

类定义可在不同的源文件之间进行拆分。

同:

接口、类和结构可从多个接口继承。

接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。

接口可以包含事件、索引器、方法和属性。

一个类可以实现多个接口。