zoukankan      html  css  js  c++  java
  • 面向接口编程详解(一)——思想基础

    我想,对于各位使用面向对象编程语言的程序员来说,“接口”这个名词一定不陌生,但是不知各位有没有这样的疑惑:接口有什么用途?它和抽象类有什么区别?能不能用抽象类代替接口呢?而且,作为程序员,一定经常听到“面向接口编程”这个短语,那么它是什么意思?有什么思想内涵?和面向对象编程是什么关系?本文将一一解答这些疑问。

    1.面向接口编程和面向对象编程是什么关系

    首先,面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。

    2.接口的本质

    接口,在表面上是由几个没有主体代码的方法定义组成的集合体,有唯一的名称,可以被类或其他接口所实现(或者也可以说继承)。它在形式上可能是如下的样子:

    interface InterfaceName
    {
        void Method1();
        void Method2(int para1);
        void Method3(string para2,string para3);
    }

    那么,接口的本质是什么呢?或者说接口存在的意义是什么。我认为可以从以下两个视角考虑:

    1)接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。

    例如,在自然界中,人都能吃饭,即“如果你是人,则必须能吃饭”。那么模拟到计算机程序中,就应该有一个IPerson(习惯上,接口名由“I”开头)接口,并有一个方法叫Eat(),然后我们规定,每一个表示“人”的类,必须实现IPerson接口,这就模拟了自然界“如果你是人,则必须能吃饭”这条规则。

    从这里,我想各位也能看到些许面向对象思想的东西。面向对象思想的核心之一,就是模拟真实世界,把真实世界中的事物抽象成类,整个程序靠各个类的实例互相通信、互相协作完成系统功能,这非常符合真实世界的运行状况,也是面向对象思想的精髓。

    2)接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。

    例如,在我的眼里,我是一个人,和一头猪有本质区别,我可以接受我和我同学是同类这个说法,但绝不能接受我和一头猪是同类。但是,如果在一个动物学家眼里,我和猪应该是同类,因为我们都是动物,他可以认为“人”和“猪”都实现了IAnimal这个接口,而他在研究动物行为时,不会把我和猪分开对待,而会从“动物”这个较大的粒度上研究,但他会认为我和一棵树有本质区别。

    现在换了一个遗传学家,情况又不同了,因为生物都能遗传,所以在他眼里,我不仅和猪没区别,和一只蚊子、一个细菌、一颗树、一个蘑菇乃至一个SARS病毒都没什么区别,因为他会认为我们都实现了IDescendable这个接口(注:descend vi. 遗传),即我们都是可遗传的东西,他不会分别研究我们,而会将所有生物作为同类进行研究,在他眼里没有人和病毒之分,只有可遗传的物质和不可遗传的物质。但至少,我和一块石头还是有区别的。

    可不幸的事情发生了,某日,地球上出现了一位伟大的人,他叫列宁,他在熟读马克思、恩格斯的辩证唯物主义思想巨著后,颇有心得,于是他下了一个著名的定义:所谓物质,就是能被意识所反映的客观实在。至此,我和一块石头、一丝空气、一条成语和传输手机信号的电磁场已经没什么区别了,因为在列宁的眼里,我们都是可以被意识所反映的客观实在。如果列宁是一名程序员,他会这么说:所谓物质,就是所有同时实现了“IReflectabe”和“IEsse”两个接口的类所生成的实例。(注:reflect v. 反映  esse n. 客观实在)

    也许你会觉得我上面的例子像在瞎掰,但是,这正是接口得以存在的意义。面向对象思想和核心之一叫做多态性,什么叫多态性?说白了就是在某个粒度视图层面上对同类事物不加区别的对待而统一处理。而之所以敢这样做,就是因为有接口的存在。像那个遗传学家,他明白所有生物都实现了IDescendable接口,那只要是生物,一定有Descend()这个方法,于是他就可以统一研究,而不至于分别研究每一种生物而最终累死。

    可能这里还不能给你一个关于接口本质和作用的直观印象。那么在后文的例子和对几个设计模式的解析中,你将会更直观体验到接口的内涵。

    3.面向接口编程综述

    通过上文,我想大家对接口和接口的思想内涵有了一个了解,那么什么是面向接口编程呢?我个人的定义是:在系统分析和架构中,分清层次和依赖关系,每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。

    这样做的好处是显而易见的,首先对系统灵活性大有好处。当下层需要改变时,只要接口及接口功能不变,则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉,就像我们将一个WD的60G硬盘换成一个希捷的160G的硬盘,计算机其他地方不用做任何改动,而是把原硬盘拔下来、新硬盘插上就行了,因为计算机其他部分不依赖具体硬盘,而只依赖一个IDE接口,只要硬盘实现了这个接口,就可以替换上去。从这里看,程序中的接口和现实中的接口极为相似,所以我一直认为,接口(interface)这个词用的真是神似!

    使用接口的另一个好处就是不同部件或层次的开发人员可以并行开工,就像造硬盘的不用等造CPU的,也不用等造显示器的,只要接口一致,设计合理,完全可以并行进行开发,从而提高效率。

    本篇文章先到这里。最后我想再啰嗦一句:面向对象的精髓是模拟现实,这也可以说是我这篇文章的灵魂。所以,多从现实中思考面向对象的东西,对提高系统分析设计能力大有脾益。

    下篇文章,我将用一个实例来展示接口编程的基本方法。

    而第三篇,我将解析经典设计模式中的一些面向接口编程思想,并解析一下.NET分层架构中的面向接口思想。

    对本文的补充:

    仔细看了各位的回复,非常高兴能和大家一起讨论技术问题。感谢给出肯定的朋友,也要感谢提出意见和质疑的朋友,这促使我更深入思考一些东西,希望能借此进步。在这里我想补充一些东西,以讨论一些回复中比较集中的问题。

    1.关于“面向接口编程”中的“接口”与具体面向对象语言中“接口”两个词

    看到有朋友提出“面向接口编程”中的“接口”二字应该比单纯编程语言中的interface范围更大。我经过思考,觉得很有道理。这里我写的确实不太合理。我想,面向对象语言中的“接口”是指具体的一种代码结构,例如C#中用interface关键字定义的接口。而“面向接口编程”中的“接口”可以说是一种从软件架构的角度、从一个更抽象的层面上指那种用于隐藏具体底层类和实现多态性的结构部件。从这个意义上说,如果定义一个抽象类,并且目的是为了实现多态,那么我认为把这个抽象类也称为“接口”是合理的。但是用抽象类实现多态合理不合理?在下面第二条讨论。

    概括来说,我觉得两个“接口”的概念既相互区别又相互联系。“面向接口编程”中的接口是一种思想层面的用于实现多态性、提高软件灵活性和可维护性的架构部件,而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段

    2.关于抽象类与接口

    看到回复中这是讨论的比较激烈的一个问题。很抱歉我考虑不周没有在文章中讨论这个问题。我个人对这个问题的理解如下:

    如果单从具体代码来看,对这两个概念很容易模糊,甚至觉得接口就是多余的,因为单从具体功能来看,除多重继承外(C#,Java中),抽象类似乎完全能取代接口。但是,难道接口的存在是为了实现多重继承?当然不是。我认为,抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。所以,如果你在为某个地方该使用接口还是抽象类而犹豫不决时,那么可以想想你的动机是什么。

    看到有朋友对IPerson这个接口的质疑,我个人的理解是,IPerson这个接口该不该定义,关键看具体应用中是怎么个情况。如果我们的项目中有Women和Man,都继承Person,而且Women和Man绝大多数方法都相同,只有一个方法DoSomethingInWC()不同(例子比较粗俗,各位见谅),那么当然定义一个AbstractPerson抽象类比较合理,因为它可以把其他所有方法都包含进去,子类只定义DoSomethingInWC(),大大减少了重复代码量。

    但是,如果我们程序中的Women和Man两个类基本没有共同代码,而且有一个PersonHandle类需要实例化他们,并且不希望知道他们是男是女,而只需把他们当作人看待,并实现多态,那么定义成接口就有必要了。

    总而言之,接口与抽象类的区别主要在于使用的动机,而不在于其本身。而一个东西该定义成抽象类还是接口,要根据具体环境的上下文决定

    再者,我认为接口和抽象类的另一个区别在于,抽象类和它的子类之间应该是一般和特殊的关系,而接口仅仅是它的子类应该实现的一组规则。(当然,有时也可能存在一般与特殊的关系,但我们使用接口的目的不在这里)如,交通工具定义成抽象类,汽车、飞机、轮船定义成子类,是可以接受的,因为汽车、飞机、轮船都是一种特殊的交通工具。再譬如Icomparable接口,它只是说,实现这个接口的类必须要可以进行比较,这是一条规则。如果Car这个类实现了Icomparable,只是说,我们的Car中有一个方法可以对两个Car的实例进行比较,可能是比哪辆车更贵,也可能比哪辆车更大,这都无所谓,但我们不能说“汽车是一种特殊的可以比较”,这在文法上都不通。


    原文http://www.cnblogs.com/leoo2sk/archive/2008/04/10/1146447.html

    部分内容由Percy整理

    个人觉得评论中也有说的很有道理的,就引用了部分评论:

     

    1#
    不太认同,说抽象类相比接口有利于代码复用是正确的,但说接口的目的是实现多态,至少在多态的层面上,接口与抽象类都有体现,抽象类的virual方法难道就不是多态么?所以说接口是多态,抽象类也是多态。从设计目的上来说,接口和抽象类,本身就具有者非常类似的目的。那就是都是想制定一套规则,将具体问题进行抽象,归纳。但接口与抽象类还是有一定区别: 
    1)二者不在一个层级 
     抽象类可以实现接口,但接口却无法继承抽象类吧?所以严格来说,接口是抽 象类的上级。 
    2)作用域不同 
    接口体现了的是相同类别或者不同类别下事物的归纳,而抽象类则更多的体现在对相同类别事务的归纳上。这在编程中的体现就是一个类可以实现多个接口,而不能继承多个抽象类,因为一个人能有各种的功能,这些功能可能是人类特有的,也可能是和其他动物共有的,但绝对不能有一个以上的亲生父亲。 
    3)开放性有所不同 
     接口和抽象类虽然相似,但在某些场合,如分布式开发中,接口就更受欢迎,特别是面向服务中,更关注的是能干什么?而相比之下,是什么不太重要。是骡子,是马,是头驴,反正能拉车就可以。所以我认为接口相比抽象类更开放一点,更适合互操作一点。 


    2#
    抽象类是定义一些共性的东西,凡是这个类别的都会有的属性和方法 
    比如人就应该是一个抽象类,人都有身高属性,获得身高的方法也是一致的,如果定义为接口,那么男人和女人都要重写一遍获得身高的方法, 
    这个不论是从代码利用,还是OO的理解上来说,都应该是抽象类 
    还有你说的吃饭做为接口并不合适,因为吃饭是一个共性的动作,是人就会吃饭,这个吃的方法应该是作为抽象方法,因为每个人吃饭的方法可能不同,有人吞着吃,有人咬着吃等等,所以就要继承人这个类的子类来具体实现。 

    而接口是定义一些特性的规则,也就是比较特殊的东西,比如有的人会跳舞,有的人会游泳 
    那么就应该定义一个跳舞的接口,一个游泳的接口 
    如果男人实现了跳舞的接口,那么这个男人就会跳舞了 
    但是并不是每个男人,或者每个人都会跳舞 

    楼上有一个人说的言简意赅: 
    行为的抽象,是规则,接口, (是特殊的个性) 
    事物的抽象,是共性,是抽象类(是一般的共性)

    3#
    如果只是为了代码复用,才用抽象类, 
    没有复用的情况,就用接口 
    根本就是背离OO。 
    抽象类和接口都是由于OO才引入的概念, 

    当然如果你程序中的Women和Man两个类基本没有共同代码,可以使用接口实现,可是你考虑到了扩展或者需求变更了情况吗? 

    如果你的项目一开始所有的需求都定型了,不会变化,而且也没有扩展的可能 
    那么你用什么都无所谓了 
    但是这可能吗?任何一个做过不少项目的都知道,需求的变更很常见,也许今天没有共同代码,明天一变,又有了,你是不是又要为了代码复用,去修改接口为抽象类,或者不改结构,在子类分别写相同的代码? 

    如果一开始就从OO的角度去设计,上面的问题很多都可以得到解决,再举一个例子吧: 
    比如门,有门锁这个属性(抽象的,因为门可以使用任何一种锁),但是锁门这个方法,就是简单一句话, 
    锁.Close(); 
    所以这里门就应该设计为抽象类, 
    锁在目前这个需求里可以是一个抽象类,也可以是接口,实现Close方法。 
    但是如果考虑到扩展,比如锁,在以后会不会可能增加钥匙这个抽象属性,同时增加钥匙的Open()方法来开门呢? 


    最后给大家一个关于接口的链接,可以深刻的理解下编程过程中的所谓的接口http://www.cnblogs.com/qianyz/archive/2010/09/28/1837745.html



  • 相关阅读:
    2429: [HAOI2006]聪明的猴子
    1789: [Ahoi2008]Necklace Y型项链
    3399: [Usaco2009 Mar]Sand Castle城堡
    3713: [PA2014]Iloczyn
    1907: 树的路径覆盖
    2751: [HAOI2012]容易题(easy)
    算法模板——计算几何2(二维凸包——Andrew算法)
    算法模板——splay区间反转 2
    算法模板——Dinic网络最大流 2
    算法模板——Dinic最小费用最大流
  • 原文地址:https://www.cnblogs.com/Percy_Lee/p/4827039.html
Copyright © 2011-2022 走看看