Live软件开发面面谈——接口

Posted starrow

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Live软件开发面面谈——接口相关的知识,希望对你有一定的参考价值。

第1章  接口


在面向对象编程中,我们将问题拆分成一个个对象来实现,每个对象有其负责的功能,多个对象合作才能形成一个有用的系统。合作在代码中就表现为对象之间的引用和方法调用。引用者对被引用者的关系称为依赖。依赖关系意味着被引用者的变化可能影响和破坏引用者原本正常的运行。当系统变得越来越大,对象越来越多,牵涉方越来越广,持续的时间越长时,设计者就希望这样牵一发而动全身的影响尽可能地小。换句话说,就是希望能消除对象之间的依赖。引用者既要调用被引用者的方法,又不能产生对它的依赖,解决方法便是运用接口。
接口的理念在编程中由来已久,在Java、C#等主流语言中更是引入了原生的Interface结构,类库中也有大量现成的接口。然而单纯使用、甚至定义接口,并不能达到消除依赖的目的。广为提倡的尽量使用接口编程,有什么好处?真正能消除依赖的针对接口编程又如何实现?它与常用的工厂模式、服务定位器模式和依赖注入有什么关系?最后,什么时候才有必要针对接口编程?在本章讨论这些问题的过程中,接口、依赖、若干设计模式、配置文件、惯例、元数据等理念的含义将得到深入的挖掘和思考。

1.1  使用接口编程

我们先来看看在用Java、C#这样的面向对象语言编程时,经常被提倡的尽量使用接口的理念。在用继承基类和实现接口构建的类型层次体系中,越往上的类型越一般和抽象,越往下的类型越具体和多功能。在我们定义变量时,无论是类字段、方法变量,还是方法的参数和返回值,都尽可能使用抽象的类型。因为像Java的语言只支持单个基类,类型的大量抽象继承是以接口的方式体现,导致在一个类的层次体系的高层,接口往往比类多,所以尽可能使用的抽象类型就以接口居多,我们不妨把这种理念称为使用接口编程。例如下面的C#代码。

//Starrow.IdeaDemo.UseInterfaceDemo
using System.Collections;

namespace Starrow.IdeaDemo

    public class UseInterfaceDemo
    
        Hashtable DeclareAndReturnConcreteClass()
        
            Hashtable hashtable = new Hashtable();
            //...
            return hashtable;
        

        void PassConcreteClass(Hashtable hashtable)
        
            hashtable.Add("a", 1);
            //...
        

        IDictionary DeclareAndReturnInterface()
        
            IDictionary dict = new Hashtable();
            //...
            return dict;
        

        void PassInterface(IDictionary dict)
        
            dict.Add("a", 1);
            //...
        
    

//End of Starrow.IdeaDemo.UseInterfaceDemo

在以ConcreteClass结尾的两个方法中,使用的是具体的类型Hashtable;而在以Interface结尾的两个方法中,使用的是抽象的接口IDictionary。使用接口的好处是,对于DeclareAndReturnInterface方法,将来如果由于业务逻辑或性能的考虑,觉得应该采用另一个更合适的实现IDictionary的类,如SortedList,只需要把dict变量初始化成一个SortedList,后面的代码和返回的类型丝毫不受影响,因而对调用方是透明的;对于PassInterface方法,能够接受任何实现了IDictionary接口的参数,调用方传入的具体类型发生变动不会影响该方法的运作。简而言之,就是使用的类型越一般,代码的应用范围越广,适应性越好。当然,应该是尽可能一般,而不是无条件的最一般。所谓可能,就是指该类型的接口能够满足使用者的需求。例如上面的例子中我们不能使用比IDictionary更一般的接口ICollection,因为它就没有Add方法。

1.2  依赖反转原则

在模块化或组件化软件设计中,不同模块间保持松耦合。每个模块都定义有清晰的接口,模块间的调用都通过和限于接口,只要接口不变,模块内对接口的实现可以自由修改和演化。这个原则就是著名的“针对接口编程,而不是针对实现”(Program to an interface, not an implementation.)。针对接口编程比使用接口编程更具雄心野心企图心(More ambitious),它要更彻底地消除依赖关系。上一节的DeclareAndReturnInterface方法,虽然dict变量声明为IDictionary接口,但因为初始化为Hashtable类型,该方法所在的类UseInterfaceDemo仍然依赖Hashtable类。要消除此依赖,必须做到模块之间完全以接口交流。

为了分析依赖和接口的关系,我们来看最简单的两个模块的情况。应用模块需要使用工具模块的功能。按照传统的设计,应用模块直接引用工具模块。两者的依赖关系如图1.1所示。

图1.1  传统设计中应用模块和工具模块的关系

这种紧密的耦合限制了应用模块的可用性,它只能和特定的工具模块一同工作,当有更好的或实现其他功能的工具模块时,它也不能替换以利用。为了打破这个约束,我们可以将应用模块需要的工具模块的功能抽象成一个工具接口,应用模块通过这个接口来使用工具模块,工具模块只要实现这个接口,就能被自由替换。此时假如将工具接口置于应用模块内,因为工具模块要引用该接口,两模块间的依赖关系发生了奇妙的倒转,如图1.2所示。

图1.2  工具接口置于应用模块之内时两模块之间倒转的关系

每个工具模块在开发时都要引用应用模块,当然是不理想的,尤其作为工具模块根本无法知道要使用它们的应用模块可能是什么样的,所以这样反过来的依赖也必须消除。办法就是令工具接口脱离应用模块,成为一个新的独立模块。这样应用模块和工具模块都仅仅引用这个抽象的接口模块。三者的关系如图1.3所示。

图1.3  工具接口独立于应用模块和工具模块时三者的关系

这个最终方案可以用一句话来概括:调用模块不应该依赖被调用模块,两者应该依赖抽象出的接口。这个原则被称为依赖反转(Dependency inversion)。

1.3  如何实现

1.3.1  工厂模式

1.3.2  服务定位器模式

1.3.3  依赖注入

1.4  真的实现了吗

1.4.1  依赖的传递性

1.4.2  依赖的注入

1.5  真正的实现

1.5.1  配置文件

1.5.2  配置代码

1.5.3  惯例先于配置

1.5.4  元数据

1.5.5  实现消除依赖的方法的本质

1.6  有必要针对接口编程吗

1.6.1  针对接口编程的成本

1.6.2  接口的意义

1.6.3  何时针对接口编程

更多精彩内容,请参看拙著:

《Live软件开发面面谈》(京东)

《Live软件开发面面谈》(当当)

《Live软件开发面面谈》(天猫)

以上是关于Live软件开发面面谈——接口的主要内容,如果未能解决你的问题,请参考以下文章

Live软件开发面面谈——事件

Live软件开发面面谈——MVC

ruby简单的基础 4

面面俱到的Java接口自动化测试实战

#Android-中控指纹仪Live R20 开发注意事项#

关于Xilinx ZYNQ Ultrascale+ MPSoC使用原生PS端DP接口实现Live模式输出的经验分享