接口(interface)

 

接口(interface)

简单地说接口就是一种对行为的契约或者规范。比如我们一说到“笔”,那么我们就知道它一定可以用来“书写”,而不管它是铅笔还是水笔,不管它是用木制的还是塑料制的。这里的“笔”就相当于一个契约(接口),它描述了“书写”这样一个行为。只要这个对象是“笔”,那么它就一定能“书写”(而不管对象具体是什么类型的东西)。正因为有了“笔”对“书写”行为的这样一个约定,所以当我们到商店里去买钢笔时,不会再问去售货小姐“这个东西能不能用来书写”;也不会在第一次用某种牌子的铅笔之前还要先看说明书才知道它能不能够书写。

而在我们的程序里,我们可以通过接口(interface)来制定这种契约。只要对象实现这个契约,那么这个对象就一定具有契约中所规定的行为,而不用去管这个对象到底是什么。当然你可能会说,那我这个对象表面上假装实现了这个契约,但在对象里面却不具有(实现)契约中所规定的行为(就像生活中的一些伪劣产品,广告里说实现了什么,但实际上都是假的),那么我们的编译器这个时候就充当明察秋毫的“质检人员”,把你的“伪劣”对象都找出来,不让你编译通过。所以编译器是我们使用接口的保证。

先来看看C#里关于interface的语法:

申明一个接口:

public interface

{

                 void 书写();       // 申明了书写行为,但仅仅是“打了个广告”而已,没有实现

}

注意:interface里申明的方法不需要诸如publicprivate等访问修饰符,因为interface里的方法都默认是public的。(秘密的规范还有什么意义呢?)

一个类被申明为实现某个接口的格式和继承很相似:

public class 钢笔 :

{

                        public void 书写()

                        {

                                    // 具体实现书写的过程

                        }

       }

所以也会经常说类“继承”了某个接口。

那么在程序里定义接口(interface)到底有用呢?这主要体现在3个方面。

1、当我们定义了自己的类,那么类的方法(即:行为)就是你自己定义的,你必须要通过其他形式(比如文档)来告诉其他的使用者,你的类有哪些方法,能实现什么。那么有了interface,我们可以在做自己的类之前先定义一个interface,然后让自己的类来遵守(实现)这个interface,那么你只需要把这个interface交给使用者就可以了,甚至在你的类还没有coding完成之前就可以先把interface交给使用者,这样你的类的coding和使用者coding的过程可以同时进行,从而提高开发速度。

2、在我们的程序里使用interface则可以获得更好扩充性和灵活性

来个例子(还是以笔为例):

public interface

{

        void书写();

}

public class 钢笔 : 笔

{

        publicvoid书写()

        {

                ......          // 用钢笔自己的方式来实现到底怎么书写

        }   

        ......                  // 其他钢笔的属性和行为

}

public class 铅笔 : 笔

{

        publicvoid书写()

        {

                ......          // 用铅笔自己的方式来实现到底怎么书写

        }   

        ......                  // 其他铅笔笔的属性和行为

}

public class 学生

{

        // 我们传入的参数是“笔”这个规范类型,而不是具体的钢笔类型或铅笔类型

        publicvoid写作文(笔 tool)

        {

                ......

                tool.书写();

                .....

        }

}

……

public static main()

{

        钢笔 pen;                      // 申明“钢笔”的对象

        铅笔 pencil;                   // 申明“铅笔”的对象

        pen = new钢笔();              // 实例“钢笔”对象实例

        pencil = new铅笔();           // 实例“铅笔”对象实例

        学生 student = new学生();

        // 这里会有一个自动转型的过程,

        //会自动将pen和pencil向上转换成“笔”来符合 “写作文(笔 tool)” 这个方法的参数规定

        student.写作文(pen)             // 学生用钢笔写作文

        student.写作文(pencil)          // 学生用铅笔写作文

        // 当以后发明出了“激光笔”,就让用学生用激光笔来写作文,

        //而不用去修改 学生.写作文(笔 tool) 这个方法了(因为激光笔也会遵守“笔”的规范)。

}

从上面的例子可以看出来,我们的学生.写作文(笔 tool) 这个方法使用了接口作为参数,这样只要实现了该接口的类型都可以传递进去,而不用管它具体是什么类型的,这使得我们的程序更容易扩充。同样凡是接受“笔”作为参数的方法,我们都可以动态的把“钢笔”或“铅笔”传进取(至于是传“钢笔”还是“铅笔”可以有很多技巧,比如用配置文件来标志)来动态的实现不同的效果和功能,这使得我们的程序更加灵活。

3、在类似C#Java这种单根继承的语言里,可以通过使用interface来在一定程度上实现多重继承的效果。

 

最后还是说一说C#里运行时环境对接口的执行过程:
C#程序在运行中是如何使用接口的,如何访问接口函数,具体流程如下:
1、当调用一个接口的函数时,系统会去检查这个接口对应实例是什么;
2、找到这个实例后,再去找这个实例对应的实例类是什么(实例类,在虚函数一文里曾说明过);
3、根据这个实例类去检查该实例类是否和接口发生了捆绑(看是否实现了该接口,冒号后面就是);
4、好!如果实例类实现了该接口(发生了捆绑),那么它就在这个实例类中找到接口中所申明的方法的定义,然后执行该方法,然后结束。
5、如果没找到,它就继续往父类去找,直到找到第一个和接口捆绑的父类为止
6、找到后,它再检查这个父类里该方法是否是被定义为虚函数;
7、如果不是,他马上就执行这个方法,然后结束;
8、如果是,麻烦了,系统又要从头来过,去检查该最开始那个实例类里有否重载了父类里的这个虚函数…...(具体过程见上一篇《虚函数》)。

DEMO

interface I

{

        voidFunc();

}

class A : I

{

        publicvirtualvoidFunc()

        {

                Console.WriteLine("Func In A");

        }

}

classB : A , I // 注意这里,用接口来实现类似多重继承的效果

{

        publicvoidFunc()

        {

                Console.WriteLine("Func In B");

        }

}   

class C : A

{

        publicoverridevoidFunc()

        {

                Console.WriteLine("Func In C");

        }

}

class D : A

{

        publicnewvoidFunc()

        {

                Console.WriteLine("Func In D");

        }

}

   

publicstaticvoidmain()

{

        I a = newA() ; //申明了接口a,并马上和一个类的实例发生关系了

        I b = newB() ; //申明了接口b,并马上和一个类的实例发生关系了

        I c = newC() ; //申明了接口c,并马上和一个类的实例发生关系了

        I d = newD() ; //申明了接口d,并马上和一个类的实例发生关系了

        //检查a的实例类A,发现A和接口I捆绑了,所以执行A的函数Func ,结果: Func In A

a.Func();

        // 检查b的实例类B,发现B和接口I捆绑了,所以执行B的函数Func(而不会去执行父类A的,尽管A也

        // 实现I接口),结果: Func In B         

        b.Func();

        // 检查c的实例类C,发现其没有和接口I捆绑,系统继续找它的父类. 发现A和I捆绑了,他就去找

        // 函数A,发现A是虚拟函数,系统又从头来找类的实例C,发现C重载(override)了Func,好了,马

        //上执行该函数. 结果是Func In C

        c.Func();

        // 检查d的实例类D,发现其没有和接口I捆绑,系统继续找它的父类. 发现A和I捆绑了,他就去找

        // 函数A,发现A是虚拟函数,系统又从头来找类的实例D,但是D里没有重载(override)Func(而是

        // 用new覆盖了),所以又会到D的父类里找,所以还是执行A的Func(),结果是Func In A

        d.Func() ;      

}

转载于:https://www.cnblogs.com/linpengfeixgu/articles/1328680.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值