接口与接口设计原则_第1页
接口与接口设计原则_第2页
接口与接口设计原则_第3页
接口与接口设计原则_第4页
接口与接口设计原则_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、精选文档接口与接口设计原则一11种设计原则1.单一职责原则 - Single Responsibility Principle(SRP) 就一个类而言,应当仅有一个引起它变化的缘由。 职责即为“变化的缘由”。 2.开放-封闭原则 - Open Close Principle(OCP) 软件实体(类、模块、函数等)应当是可以扩展的,但是不行修改。对于扩展是开放的,对于更改是封闭的. 关键是抽象.将一个功能的通用部分和实现细节部分清楚的分别开来。开发人员应当仅仅对程序中呈现出频繁变化的那些部分作出抽象. 拒绝不成熟的抽象和抽象本身一样重要 ) 3.里氏替换原则 - Liskov Substitut

2、ion Principle(LSP) 子类型(subclass)必需能够替换掉它们的基类型(superclass)。 4.依靠倒置原则(IoCP) 或 依靠注入原则 - Dependence Inversion Principle(DIP) 抽象不应当依靠于细节。细节应当依靠于抽象。Hollywood原则: "Don't call us, we'll call you". 程序中全部的依靠关系都应当终止于抽象类和接口。针对接口而非实现编程。任何变量都不应当持有一个指向具体类的指针或引用。任何类都不应当从具体类派生。 任何方法都不应当覆写他的任何基类中的已经实

3、现了的方法。5.接口隔离原则(ISP) 不应当强迫客户依靠于它们不用的方法。接口属于客户,不属于它所在的类层次结构。多个面对特定用户的接口胜于一个通用接口。 6.重用发布等价原则(REP) 重用的粒度就是发布的粒度。 7.共同封闭原则(CCP) 包(类库、DLL)中的全部类对于同一类性质的变化应当是共同封闭的。 一个变化若对一个包产生影响, 则将对该包中的全部类产生影响, 而对于其他的包不造成任何影响。 8.共同重用原则(CRP) 一个包(类库、DLL)中的全部类应当是共同重用的。 假如重用了包(类库、DLL)中的一个类, 那么就要重用包(类库、DLL)中的全部类。 (相互之间没有紧密联系的类

4、不应当在同一个包(类库、DLL)中。) 包(类库、DLL)耦合原则 9.无环依靠原则(ADP) 在包的依靠关系图中不允许存在环。 10.稳定依靠原则(SDP) 朝着稳定的方向进行依靠。 应当把封装系统高层设计的软件(比如抽象类)放进稳定的包中,不稳定的包中应当只包含那些很可能会转变的软件(比如具体类)。 11.稳定抽象原则(SAP) 包的抽象程度应当和其稳定程度全都。一个稳定的包应当也是抽象的,一个不稳定的包应当是抽象的. 其它扩展原则 12.BBP(Black Box Principle)黑盒原则 多用类的聚合,少用类的继承。 13.DAP(Default Abstraction Princ

5、iple)缺省抽象原则 在接口和实现接口的类之间引入一个抽象类,这个类实现了接口的大部分操作. 14.IDP(Interface Design Principle)接口设计原则 规划一个接口而不是实现一个接口。 15.DCSP(Don't Concrete Supperclass Principle)  不要构造具体的超类原则,避开维护具体的超类。 16.迪米特法则  一个类只依靠其触手可得的类。 二类的设计原则  接口定义接口定义1.开闭原则Software entities (classes, modules, function, etc.)

6、 should be open for extension, but closed for modification.软件实体(模块,类,方法等)应当对扩开放放,对修改关闭。 开闭原则(OCP:Open-Closed Principle)是指在进行面对对象设计(OOD:Object Oriented Design)中,设计类或其他程序单位时,应当遵循: - 对扩开放放(open) - 对修改关闭(closed)的设计原则。 开闭原则是推断面对对象设计是否正确的最基本的原理之一。 依据开闭原则,在设计一个软件系统模块(类,方法)的时候,应当可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能

7、(扩开放放)。- 扩开放放:某模块的功能是可扩展的,则该模块是扩开放放的。软件系统的功能上的可扩展性要求模块是扩开放放的。- 修改关闭:某模块被其他模块调用,假如该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改关闭的。这也是系统设计需要遵循开闭原则的缘由:1)稳定性。开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。2)扩展性。开闭原则要求对扩开放放,通过扩展供应新的或转变原有的功能,让软件系统具有机敏的可扩展性。遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。开闭原则的实现方法为了满足开闭原则的 对修改关闭(close

8、d for modification) 原则以及扩开放放(open for extension) 原则,应当对软件系统中的不变的部分加以抽象,在面对对象的设计中,- 可以把这些不变的部分加以抽象成不变的接口,这些不变的接口可以应对将来的扩展;- 接口的最小功能设计原则。依据这个原则,原有的接口要么可以应对将来的扩展;不足的部分可以通过定义新的接口来实现;- 模块之间的调用通过抽象接口进行,这样即使实现层发生变化,也无需修改调用方的代码。接口可以被复用,但接口的实现却不肯定能被复用。接口是稳定的,关闭的,但接口的实现是可变的,开放的。可以通过对接口的不同实现以及类的继承行为等为系统增加新的或转变

9、系统原来的功能,实现软件系统的松软扩展。简洁地说,软件系统是否有良好的接口(抽象)设计是推断软件系统是否满足开闭原则的一种重要的推断基准。现在多把开闭原则等同于面对接口的软件设计。开闭原则的相对性软件系统的构建是一个需要不断重构的过程,在这个过程中,模块的功能抽象,模块与模块间的关系,都不会从一开头就格外清楚明白,所以构建100%满足开闭原则的软件系统是相当困难的,这就是开闭原则的相对性。但在设计过程中,通过对模块功能的抽象(接口定义),模块之间的关系的抽象(通过接口调用),抽象与实现的分别(面对接口的程序设计)等,可以尽量接近满足开闭原则。2.单一职责原则前言Robert C. Martin

10、氏为我们总结了在面对对象的设计(OOD)中应当遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的单一职责原则:Single Responsibility Principle (SRP)。可以从这里查看Single Responsibility Principle (SRP)的原文。概要There should never be more than one reason for a class to change.永久不要让一个类存在多个转变

11、的理由。换句话说,假如一个类需要转变,转变它的理由永久只有一个。假如存在多个转变它的理由,就需要重新设计该类。SRP(Single Responsibility Principle)原则的核心含意是:只能让一个类有且仅有一个职责。这也是单一职责原则的命名含义。为什么一个类不能有多于一个以上的职责呢?假如一个类具有一个以上的职责,那么就会有多个不同的缘由引起该类变化,而这种变化将影响到该类不同职责的使用者(不同用户):1,一方面,假如一个职责使用了外部类库,则使用另外一个职责的用户却也不得不包含这个未被使用的外部类库。2,另一方面,某个用户由于某个缘由需要修改其中一个职责,另外一个职责的用户也将

12、受到影响,他将不得不重新编译和配置。这违反了设计的开闭原则,也不是我们所期望的。职责的划分既然一个类不能有多个职责,那么怎么划分职责呢?Robert.C Martin给出了一个有名的定义:所谓一个类的一个职责是指引起该类变化的一个缘由。If you can think of more than one motive for changing a class, then that class has more than one responsibility.假如你能想到一个类存在多个使其转变的缘由,那么这个类就存在多个职责。Single Responsibility Principle (SRP

13、)的原文里举了一个Modem的例子来说明怎么样进行职责的划分,这里我们也沿用这个例子来说明一下:SRP违反例:public interface Modem     public void dial(String pno);    /拨号    public void hangup();        /挂断    public void send(char

14、c);    /发送数据    public char recv();        /接收数据咋一看,这是一个没有任何问题的接口设计。但事实上,这个接口包含了2个职责:第一个是连接管理(dial, hangup);另一个是数据通信(send, recv)。很多状况下,这2个职责没有任何共通的部分,它们由于不同的理由而转变,被不同部分的程序调用。所以它违反了SRP原则。下面的类图将它的2个不同职责分成2个不同的接口,这样至少可以让客户端

15、应用程序使用具有单一职责的接口:接口定义具体类实现让ModemImplementation实现这两个接口。我们留意到,ModemImplementation又组合了2个职责,这不是我们期望的,但有时这又是必需的。通常由于某些缘由,迫使我们不得不绑定多个职责到一个类中,但我们至少可以通过接口的分割来分别应用程序关怀的概念。事实上,这个例子一个更好的设计应当是这样的,如图:具体类实现接口定义小结Single Responsibility Principle (SRP)从职责(转变理由)的侧面上为我们对类(接口)的抽象的颗粒度建立了推断基准:在为系统设计类(接口)的时候应当保证它们的单一职责性。3接

16、口分隔原则前言Robert C. Martin氏为我们总结了在面对对象的设计(OOD)中应当遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的接口分隔原则:Interface Segregation Principle (ISP)。可以从这里查看Interface Segregation Principle (ISP)的原文。概要Clients should not be forced to depend upon interfaces t

17、hat they do not use.不能强迫用户去依靠那些他们不使用的接口。换句话说,使用多个特地的接口比使用单一的总接口总要好。它包含了2层意思:- 接口的设计原则:接口的设计应当遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。假如一个接口的方法没有被使用到,则说明该接口过胖,应当将其分割成几个功能专一的接口。- 接口的依靠(继承)原则:假如一个接口a依靠(继承)另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应当遵循上述原则:不应当包含用户不使用的方法。反之,则说明接口a被b给污染了,应当重新设计它们的关系。假如用户被迫依靠他们不使用的接口,当接口

18、发生转变时,他们也不得不跟着转变。换而言之,一个用户依靠了未使用但被其他用户使用的接口,当其他用户修改该接口时,依靠该接口的全部用户都将受到影响。这明显违反了开闭原则,也不是我们所期望的。下面我们举例说明怎么设计接口或类之间的关系,使其不违反ISP原则。假如有一个Door,有lock,unlock功能,另外,可以在Door上安装一个Alarm而使其具有报警功能。用户可以选择一般的Door,也可以选择具有报警功能的Door。有以下几种设计方法:ISP原则的违反例:方法一:在Door接口里定义全部的方法。图:但这样一来,依靠Door接口的CommonDoor却不得不实现未使用的alarm()方法。

19、违反了ISP原则。方法二:在Alarm接口定义alarm方法,在Door接口定义lock,unlock方法,Door接口继承Alarm接口。跟方法一一样,依靠Door接口的CommonDoor却不得不实现未使用的alarm()方法。违反了ISP原则。遵循ISP原则的例:方法三:通过多重继承实现在Alarm接口定义alarm方法,在Door接口定义lock,unlock方法。接口之间无继承关系。CommonDoor实现Door接口,AlarmDoor有2种实现方案:1),同时实现Door和Alarm接口。2),继承CommonDoor,并实现Alarm接口。该方案是继承方式的Adapter设计模

20、式的实现。第2)种方案更具有有用性。这种设计遵循了ISP设计原则。方法四:通过委让实现这种方法其实是委让方式的Adapter设计模式的实现。在这种方法里,AlarmDoor实现了Alarm接口,同时把功能lock和unlock委让给CommonDoor对象完成。这种设计遵循了ISP设计原则。小结Interface Segregation Principle (ISP)从对接口的使用上为我们对接口抽象的颗粒度建立了推断基准:在为系统设计接口的时候,使用多个特地的接口代替单一的胖接口。4依靠倒置原则Robert C. Martin氏为我们总结了在面对对象的设计(OOD)中应当遵循的原则,这些原则被

21、称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍DIP:Dependency Inversion Principle - 依靠倒置原则。有关Dependency Inversion Principle (DIP) 原文可以从这里 得到。该文提出了依靠倒置原则的2个重要方针:A. High level modules should not depend upon low level modules. Both should depend upon abstractions.B. Abstractions

22、 should not depend upon details. Details should depend upon abstractions.中文意思为:A. 高层模块不应当依靠于低层模块,二者都应当依靠于抽象B. 抽象不应当依靠于细节,细节应当依靠于抽象概念解说:依靠:在程序设计中,假如一个模块a使用/调用了另一个模块b,我们称模块a依靠模块b。高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些简单的规律,并且依靠于低层次的类,这些类我们称之为高层模块。为什么叫做依靠倒置(Depe

23、ndency Inversion)呢?面对对象程序设计相对于面对过程(结构化)程序设计而言,依靠关系被倒置了。由于传统的结构化程序设计中,高层模块总是依靠于低层模块。问题的提出:Robert C. Martin氏在原文中给出了“Bad Design”的定义:1. It is hard to change because every change affects too many other parts of the system.(Rigidity)系统很难转变,由于每个转变都会影响其他很多部分。2. When you make a change, unexpected parts of th

24、e system break. (Fragility)当你对某地方做一修改,系统的看似无关的其他部分都不工作了。3. It is hard to reuse in another application because it cannot be disentangled fromthe current application. (Immobility)系统很难被另外一个应用重用,由于你很难将要重用的部分从系统中分别开来。导致“Bad Design”的很大缘由是“高层模块”过分依靠“低层模块”。一个良好的设计应当是系统的每一部分都是可替换的。假如“高层模块”过分依靠“低层模块”,一方面一旦“低层

25、模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难可以重用。比如,一个Copy模块,需要把来自Keyboard的输入复制到Print,即使对Keyboard和Print的封装已经做得格外好,但假如Copy模块里直接使用Keyboard与Print,Copy任很难被其他应用环境(比如需要输出到磁盘时)重用。问题的解决:为了解决上述问题,Robert C. Martin氏提出了OO设计的Dependency Inversion Principle (DIP) 原则。DIP给出了一个解决方案:在高层模块与低层模块之间,引入一个抽象接口层。High Level Classes(高层

26、模块) -> Abstraction Layer(抽象接口层) -> Low Level Classes(低层模块)抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。这样,高层模块不直接依靠低层模块,高层模块与低层模块都依靠抽象接口层。当然,抽象也不依靠低层模块的实现细节,低层模块依靠(继承或实现)抽象定义。Robert C. Martin氏给出的DIP方案的类的结构图:PolicyLayer->MechanismInterface(abstract)-MechanismLayer->UtilityInterface(abstract)-UtilityLayer

27、类与类之间都通过Abstract Layer来组合关系。5里氏替换原则Robert C. Martin氏为我们总结了在面对对象的设计(OOD)中应当遵循的原则,这些原则被称为“Principles of OOD”,关于“Principles of OOD”的相关文章可以从Object Menter得到。本文介绍“Principles of OOD”中的里氏替换原则:Liskov Substitution Principle (LSP)。可以从这里查看Liskov Substitution Principle (LSP)的原文 。里氏替换原则LSP的概念解说Functions that use

28、pointers or references to base classes must be able to use objects of derived classes without knowing it.全部引用基类的地方必需能透亮地使用其子类的对象。也就是说,只有满足以下2个条件的OO设计才可被认为是满足了LSP原则:- 不应当在代码中消灭if/else之类对子类类型进行推断的条件。以下代码就违反了LSP定义。if (obj typeof Class1) do something else if (obj typeof Class2) do something else- 子类应当可以

29、替换父类并消灭在父类能够消灭的任何地方,或者说假如我们把代码中使用基类的地方用它的子类所代替,代码还能正常工作。里氏替换原则LSP是使代码符合开闭原则的一个重要保证。同时LSP体现了:- 类的继承原则:假如一个继承类的对象可能会在基类消灭的地方消灭运行错误,则该子类不应当从该基类继承,或者说,应当重新设计它们之间的关系。- 动作正确性保证:从另一个侧面上保证了符合LSP设计原则的类的扩展不会给已有的系统引入新的错误。类的继承原则:Robert C. Martin氏在介绍Liskov Substitution Principle (LSP)的原文里,举了Rectangle和Square的例子。这

30、里沿用这个例子,但用Java语言对其加以重写,并忽视了某些细节只列出下面的精要部分来说明 里氏替换原则 对类的继承上的约束。代码:这里Rectangle是基类,Square从Rectangle继承。这种继承关系有什么问题吗?假如已有的系统中存在以下既有的业务规律代码:void g(Rectangle r) r.setWidth(5);r.setHeight(4);if (r.getWidth() * r.getHeight() != 20) throw new RuntimeException();则对应于扩展类Square,在调用既有业务规律时:Rectangle square = new

31、Square();g(square);时会抛出一个RuntimeException特别。这明显违反了LSP原则。动作正确性保证:由于LSP对子类的约束,所以为已存在的类做扩展构造一个新的子类时,依据LSP的定义,不会给已有的系统引入新的错误。Design by Contract依据Bertrand Meyer氏提出的Design by Contract(DBC:基于合同的设计)概念的描述,对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;同时后续条件用来说明这个方法完成时的状态,假如一个方法的执行会导致这个方

32、法的后续条件不成立,那么这个方法也不应当正常返回。现在把前提条件以及后续条件应用到继承子类中,子类方法应当满足:1)前提条件不强于基类2)后续条件不弱于基类换句话说,通过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。因此继承类不得要求用户供应比基类方法要求的更强的前提条件,亦即,继承类方法必需接受任何基类方法能接受的任何条件(参数)。同样,继承类必需服从基类的全部后续条件,亦即,继承类方法的行为和输出不得违反由基类建立起来的任何约束,不能让用户对继承类方法的输出感到困惑。这样,我们就有了基于合同的LSP,基于合同的LSP是LSP的一种强化。在很多状况下,在设计初期我们类之间的关

33、系不是很明确,LSP则给了我们一个推断和设计类之间关系的基准:需不需要继承,以及怎样设计继承关系。三针对接口编程,而不是针对实现编程 在面对对象设计方法中有很多值得提倡的方法,这些方法可以为我们的设计带来很大的机敏性,可复用性。其中一个原则就是“针对接口编程,而不是针对实现编程” 这个原则带来的好处有以下几点:   Client不必知道其使用对象的具体所属类。   Client无需知道特定类,只需知道他们所期望的接口。   一个对象可以很简洁地被(实现了相同接口的)的另一个对象所替换。   对象间的连接不必硬绑定(hardwire)到一个具体类的对象上,

34、因此增加了机敏性。   松散藕合(loosens   coupling)。   增加了重用的可能性。提高了(对象)组合的机率,由于被包含对象可以是任何实现了一个指定接口的类。 但从辩证法的角度看,事物总有利有弊。“针对接口编程”有如上诸多好处,却不行避开的带来设计的简单性。特殊对于没有丰富阅历的设计人员。 其中令我比较困惑的地方是: 要想针对接口编程,就必定要最大化接口类,使包括全部子类的方法,这样我们才能利用多态性用接口类来实现操作子类。但这会带来以下几点不足。   违反面对对象的另一个原则,这个原则是:一个类只能定义那些对它的子类有意义的操作。 &#

35、160; 接口类包括了并不是对每一个子类都有意义的方法,使接口类臃肿,难以理解。   从父类继承的无用方法,如何处理。  灵敏开发提倡简洁设计的实践,“并在实现新需求时抓住机会改进设计”以对同类性质的改动封闭,做到由需求的变化驱动设计的进化(我们不能由于设计的退化而责怪需求的变化),同时阅历在此起到格外重要的作用,如有阅历的设计人员可以凭阅历在初始设计时做出必要的抽象来满足ocp原则等,或是在需求变动时确定系统所需的抽象(所需的封闭),当然应及早的刺激这种变化的消灭(如测试驱动的开发方法)。OOD承诺了一系列的好处(机敏性可重用性可维护性),用OO语言设计开发,若要便利的得

36、到这些所谓的好处,有一系列的原则是要遵循的,如SRP,OCP,LSP,ISP等。 SRP(单一职责原则)维护类的简洁性,类不应担当一个以上令其变化的缘由,否则应考虑分别并重新构造类,但假如的应用的变化方式总是导致类中的职责同时变化,却没必要分别他们Ocp(开闭原则)使OO系统做到对扩开放放,对修改封闭。OCP的遵循关键在于抽象,其主要实现方式有:定义接口描绘所需的操作,client只需关注接口的调用,子类型可以以任何其选择的方式实现接口,即所谓的stategy模式,或者定义抽象类并于其中实现公共操作,共性操作定义为abstract或virtual,由子类型负责共性化实现,通过此两法,将功能的通

37、用部分和实现细节分别出来。当然,设计人员应当确定(猜想或凭阅历)系统对哪种变化做到封闭,由于不行能对全部变化做到封闭,如灵敏模式实践中提到shape类型排序处理问题,为做到对排序支配(或变动)的封闭(使得各子类型间无需相互知晓,也可以做到自由支配排序挨次),选择使用“数据驱动”的方式(即单独构造结构表示排序支配其中以子类类型在结构中的排列位置表先后),于shape基类中实现一次Precedes操作即可,子类型无需分别实现。OCP作为OOD核心所在依靠抽象来实现,但灵敏设计(或者说好的设计)拒绝不成熟的抽象,程序仅应对频繁变化的部分做出抽象。Liskov替换原则是使得ocp成为可能的原则之一,强

38、调“子类型subtype必需能够替换掉它们的基类型basetype”,把握OO的继承关系支配,在OOD用is-a来确定类间的继承关系,LSP指出这种is-a关系是就行为方式(即类的各操作)而言的,而行为方式是可以合理假设的,是客户程序所依靠的。为遵循LSP,可借用DBC(design by contract) 的操作前置条件和后置条件,“要使操作得以执行,前置条件必需为真,执行完毕后,该操作要确保后置条件为真”(为每个方法注明其前置和后置条件格外有帮 助),如此,则“在重新声明派生类中操作时,只能使用相等或更弱的前置条件来替换原始的前置条件,只能用相等或更强的后置条件来替换原始的后置条件”(i

39、nterface和其实现类间 抽象方法和其实现 此二者肯定满足前述条件)。同时亦可用前述ocp遵循所用的二模式使设计符合LSP,另外子类型中的特别抛出应考虑在遵循LSP的范围内。关于提取公共部分的设计工具:“提取公共职责放入超类中,稍后添加的新的子类型可能会以新的方式支持同样的职责,此时原来的超类可能会是一个抽象类”。  DIP(依靠倒置原则),作为framework的设计核心,其相对于传统软件设计而言,通常(传统)软件设计中接受结构化设计用高层模块直接调用底层模块,这样高层模块将严峻依靠于底层模块的变动,在OOD中通过为高层模块定义所需使用的服务接口,底层模块现实这样的接

40、口,高层模块通过抽象接口使用下一层(strategy模式所声明的),如此看来接口的拥有者一般是其使用者而非其实现者。通常为了满足DIP-良好OOD的基本底层机制,我么需要找出系统中潜在的抽象,而抽象通常是那些不随具体细节变化而变化的东东。  ISP接口隔离原则,如SRP维护类的简洁性一样,ISP用于维护接口的简洁和必要性,由于接口是为客户调用的,因此其应当是“大小尺寸合适的”,“胖”接口明显对调用者造成累赘,ISP则用于将“胖”接口分别成多个合适的接口。  当然,在系统设计实现中要做到这些并非简洁,单单知道其存在未必做得到将其实现到系统中,开发阅历的积累

41、同样重要,但早些知道存在个意识并在做时将其考虑进去,也是积累,渐渐来吧,n多事要做呐。四面对接口编程详解思想基础   我想,对于各位使用面对对象编程语言的程序员来说,“接口”这个名词肯定不生疏,但是不知各位有没有这样的怀疑:接口有什么用途?它和抽象类有什么区分?能不能用抽象类代替接口呢?而且,作为程序员,肯定经常听到“面对接口编程”这个短语,那么它是什么意思?有什么思想内涵?和面对对象编程是什么关系?本文将一一解答这些疑问。1.面对接口编程和面对对象编程是什么关系  首先,面对接口编程和面对对象编程并不是平级的,它并不是比面对对象编程更先进的一种独立的编

42、程思想,而是附属于面对对象思想体系,属于其一部分。或者说,它是面对对象编程体系中的思想精髓之一。2.接口的本质  接口,在表面上是由几个没有主体代码的方法定义组成的集合体,有唯一的名称,可以被类或其他接口所实现(或者也可以说继承)。它在形式上可能是如下的样子:interface InterfaceName    void Method1();    void Method2(int para1);    void&#

43、160;Method3(string para2,string para3);  那么,接口的本质是什么呢?或者说接口存在的意义是什么。我认为可以从以下两个视角考虑: 1)接口是一组规章的集合,它规定了实现本接口的类或接口必需拥有的一组规章。体现了自然界“假如你是则必需能”的理念。  例如,在自然界中,人都能吃饭,即“假如你是人,则必需能吃饭”。那么模拟到计算机程序中,就应当有一个IPerson(习惯上,接口名由“I”开头)接口,并有一个方法叫Eat(),然后我们规定,每一个表示“人”的类,必需实现IPerson接口,这就模拟了自然界“假如你是

44、人,则必需能吃饭”这条规章。  从这里,我想各位也能看到些许面对对象思想的东西。面对对象思想的核心之一,就是模拟真实世界,把真实世界中的事物抽象成类,整个程序靠各个类的实例相互通信、相互协作完成系统功能,这格外符合真实世界的运行状况,也是面对对象思想的精髓。  2)接口是在肯定粒度视图上同类事物的抽象表示。留意这里我强调了在肯定粒度视图上,由于“同类事物”这个概念是相对的,它由于粒度视图不同而不同。  例如,在我的眼里,我是一个人,和一头猪有本质区分,我可以接受我和我同学是同类这个说法,但绝不能接受我和一头猪是同类。但是,假如在一个动物学家眼里

45、,我和猪应当是同类,由于我们都是动物,他可以认为“人”和“猪”都实现了IAnimal这个接口,而他在争辩动物行为时,不会把我和猪分开对待,而会从“动物”这个较大的粒度上争辩,但他会认为我和一棵树有本质区分。  现在换了一个遗传学家,状况又不同了,由于生物都能遗传,所以在他眼里,我不仅和猪没区分,和一只蚊子、一个细菌、一颗树、一个蘑菇乃至一个SARS病毒都没什么区分,由于他会认为我们都实现了IDescendable这个接口(注:descend vi. 遗传),即我们都是可遗传的东西,他不会分别争辩我们,而会将全部生物作为同类进行争辩,在他眼里没有人和病毒之分,只有可

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

47、实在)  或许你会觉得我上面的例子像在瞎掰,但是,这正是接口得以存在的意义。面对对象思想和核心之一叫做多态性,什么叫多态性?说白了就是在某个粒度视图层面上对同类事物不加区分的对待而统一处理。而之所以敢这样做,就是由于有接口的存在。像那个遗传学家,他明白全部生物都实现了IDescendable接口,那只要是生物,肯定有Descend()这个方法,于是他就可以统一争辩,而不至于分别争辩每一种生物而最终累死。   可能这里还不能给你一个关于接口本质和作用的直观印象。那么在后文的例子和对几个设计模式的解析中,你将会更直观体验到接口的内涵。3.面对接口编

48、程综述  通过上文,我想大家对接口和接口的思想内涵有了一个了解,那么什么是面对接口编程呢?我个人的定义是:在系统分析和架构中,分清层次和依靠关系,每个层次不是直接向其上层供应服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依靠,而不依靠具体类。  这样做的好处是显而易见的,首先对系统机敏性大有好处。当下层需要转变时,只要接口及接口功能不变,则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉,就像我们将一个WD的60G硬盘换成一个希捷的160G的硬盘,计算机其他地方不用做任何改动,而是把原

49、硬盘拔下来、新硬盘插上就行了,由于计算机其他部分不依靠具体硬盘,而只依靠一个IDE接口,只要硬盘实现了这个接口,就可以替换上去。从这里看,程序中的接口和现实中的接口极为相像,所以我始终认为,接口(interface)这个词用的真是神似!  使用接口的另一个好处就是不同部件或层次的开发人员可以并行开工,就像造硬盘的不用等造CPU的,也不用等造显示器的,只要接口全都,设计合理,完全可以并行进行开发,从而提高效率。  本篇文章先到这里。最终我想再啰嗦一句:面对对象的精髓是模拟现实,这也可以说是我这篇文章的灵魂。所以,多从现实中思考面对对象的东西,对提高系统分析

50、设计力量大有脾益。  认真看了各位的回复,格外兴奋能和大家一起争辩技术问题。感谢给出确定的伴侣,也要感谢提出意见和质疑的伴侣,这促使我更深化思考一些东西,期望能借此进步。在这里我想补充一些东西,以争辩一些回复中比较集中的问题。1.关于“面对接口编程”中的“接口”与具风光对对象语言中“接口”两个词  看到有伴侣提出“面对接口编程”中的“接口”二字应当比单纯编程语言中的interface范围更大。我经过思考,觉得很有道理。这里我写的的确不太合理。我想,面对对象语言中的“接口”是指具体的一种代码结构,例如C#中用interface关键字定义的接口。而“面对接口

51、编程”中的“接口”可以说是一种从软件架构的角度、从一个更抽象的层面上指那种用于隐蔽具体底层类和实现多态性的结构部件。从这个意义上说,假如定义一个抽象类,并且目的是为了实现多态,那么我认为把这个抽象类也称为“接口”是合理的。但是用抽象类实现多态合理不合理?在下面其次条争辩。  概括来说,我觉得两个“接口”的概念既相互区分又相互联系。“面对接口编程”中的接口是一种思想层面的用于实现多态性、提高软件机敏性和可维护性的架构部件,而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段。2.关于抽象类与接口  看到回复中这是争辩的比较激烈的一个问题。很愧疚

52、我考虑不周没有在文章中争辩这个问题。我个人对这个问题的理解如下:  假如单从具体代码来看,对这两个概念很简洁模糊,甚至觉得接口就是多余的,由于单从具体功能来看,除多重继承外(C#,Java中),抽象类好像完全能取代接口。但是,莫非接口的存在是为了实现多重继承?当然不是。我认为,抽象类和接口的区分在于使用动机。使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态性。所以,假如你在为某个地方该使用接口还是抽象类而迟疑不决时,那么可以想想你的动机是什么。  看到有伴侣对IPerson这个接口的质疑,我个人的理解是,IPerson这个接口该不该定义,关键看

53、具体应用中是怎么个状况。假如我们的项目中有Women和Man,都继承Person,而且Women和Man绝大多数方法都相同,只有一个方法DoSomethingInWC()不同(例子比较粗俗,各位见谅),那么当然定义一个AbstractPerson抽象类比较合理,由于它可以把其他全部方法都包含进去,子类只定义DoSomethingInWC(),大大削减了重复代码量。  但是,假如我们程序中的Women和Man两个类基本没有共同代码,而且有一个PersonHandle类需要实例化他们,并且不期望知道他们是男是女,而只需把他们当作人看待,并实现多态,那么定义成接口就有必要了。&#

54、160; 总而言之,接口与抽象类的区分主要在于使用的动机,而不在于其本身。而一个东西该定义成抽象类还是接口,要依据具体环境的上下文打算。  再者,我认为接口和抽象类的另一个区分在于,抽象类和它的子类之间应当是一般和特殊的关系,而接口仅仅是它的子类应当实现的一组规章。(当然,有时也可能存在一般与特殊的关系,但我们使用接口的目的不在这里)如,交通工具定义成抽象类,汽车、飞机、轮船定义成子类,是可以接受的,由于汽车、飞机、轮船都是一种特殊的交通工具。再譬如Icomparable接口,它只是说,实现这个接口的类必需要可以进行比较,这是一条规章。假如Car这个类实现了Ico

55、mparable,只是说,我们的Car中有一个方法可以对两个Car的实例进行比较,可能是比哪辆车更贵,也可能比哪辆车更大,这都无所谓,但我们不能说“汽车是一种特殊的可以比较”,这在文法上都不通。五面对接口编程详解问题的提出 定义:现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与U盘、MP3、移动硬盘等设备进行数据交换。  上下文(环境):已知要实现U盘、MP3播放器、移动硬盘三种移动存储设备,要求计算机能同这三种设备进行数据交换,并且以后可能会有新的第三方的移动存储设备,所以计算机必需有扩展性,能与目前未知而以后可能会消灭的存储设备进行数据交换。 

56、60;各个存储设备间读、写的实现方法不同,U盘和移动硬盘只有这两个方法,MP3Player还有一个PlayMusic方法。  名词定义:数据交换=读,写  看到上面的问题,我想各位脑子中肯定有了不少想法,这是个很好解决的问题,很多方案都能达到效果。下面,我列举几个典型的方案。解决方案列举  方案一:分别定义FlashDisk、MP3Player、MobileHardDisk三个类,实现各自的Read和Write方法。然后在Computer类中实例化上述三个类,为每个类分别写读、写方法。例如,为FlashDisk写ReadFromFla

57、shDisk、WriteToFlashDisk两个方法。总共六个方法。  方案二:定义抽象类MobileStorage,在里面写虚方法Read和Write,三个存储设备继承此抽象类,并重写Read和Write方法。Computer类中包含一个类型为MobileStorage的成员变量,并为其编写get/set器,这样Computer中只需要两个方法:ReadData和WriteData,并通过多态性实现不同移动设备的读写。  方案三:与方案二基本相同,只是不定义抽象类,而是定义接口IMobileStorage,移动存储器类实现此接口。Computer中通

58、过依靠接口IMobileStorage实现多态性。  方案四:定义接口IReadable和IWritable,两个接口分别只包含Read和Write,然后定义接口IMobileStorage接口继承自IReadable和IWritable,剩下的实现与方案三相同。  下面,我们来分析一下以上四种方案:   首先,方案一最直白,实现起来最简洁,但是它有一个致命的弱点:可扩展性差。或者说,不符合“开放-关闭原则”(注:意为对扩开放放,对修改关闭)。当将来有了第三方扩展移动存储设备时,必需对Computer进行修改。这就如在一个真实的计算机

59、上,为每一种移动存储设备实现一个不同的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后,我们就要将计算机大卸八块,然后增加一个新的插口,在编写一套针对此新设备的驱动程序。这种设计明显不行取。  此方案的另一个缺点在于,冗余代码多。假如有100种移动存储,那我们的Computer中岂不是要至少写200个方法,这是不能接受的!  我们再来看方案二和方案三,之所以将这两个方案放在一起争辩,是由于他们基本是一个方案(从思想层面上来说),只不过实现手段不同,一个是使用了抽象类,一个是使用了接口,而且最终达到的目的应当是一样的。  我们

60、先来评价这种方案:首先它解决了代码冗余的问题,由于可以动态替换移动设备,并且都实现了共同的接口,所以不管有多少种移动设备,只要一个Read方法和一个Write方法,多态性就帮我们解决问题了。而对第一个问题,由于可以运行时动态替换,而不必将移动存储类硬编码在Computer中,所以有了新的第三方设备,完全可以替换进去运行。这就是所谓的“依靠接口,而不是依靠与具体类”,不信你看看,Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于我们在运行时给这个变量的赋值。如此一来,Computer和移动存储器类

61、的耦合度大大下降。  那么这里该选抽象类还是接口呢?还记得第一篇文章我对抽象类和接口选择的建议吗?看动机。这里,我们的动机明显是实现多态性而不是为了代码复用,所以当然要用接口。   最终我们再来看一看方案四,它和方案三很类似,只是将“可读”和“可写”两个规章分别抽象成了接口,然后让IMobileStorage再继承它们。这样做,明显进一步提高了机敏性,但是,这有没有设计过度的嫌疑呢?我的观点是:这要看具体状况。假如我们的应用中可能会消灭一些类,这些类只实现读方法或只实现写方法,如只读光盘,那么这样做也是可以的。假如我们知道以后消灭的东西都是能读又能写的,

62、那这两个接口就没有必要了。其实假如将只读设备的Write方法留空或抛出特别,也可以不要这两个接口。总之一句话:理论是死的,人是活的,一切从现实需要来,防止设计不足,也要防止设计过度。  在这里,我们姑且认为以后的移动存储都是能读又能写的,所以我们选方案三。实现  下面,我们要将解决方案加以实现。我选择的语言是C#,但是在代码中不会用到C#特有的性质,所以使用其他语言的伴侣一样可以参考。首先编写IMobileStorage接口:1namespace InterfaceExample23    public&

63、#160;interface IMobileStorage4    5        void Read();/从自身读数据6        void Write();/将数据写入自身7    8比较简洁,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:U盘 1namespace Inte

64、rfaceExample 2 3    public class FlashDisk : IMobileStorage 4     5        public void Read() 6         7  

65、60;         Console.WriteLine("Reading from FlashDisk"); 8            Console.WriteLine("Read finished!"); 9       

66、 1011        public void Write()12        13            Console.WriteLine("Writing to FlashDisk");14            Console.W

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论