1.6 接口设计

软件的未来其实在很大程度上要看软件接口的前景如何。我们知道,计算机世界里的“接口”一词具有两种众所周知的含义:其一是指软件本身的狭义“接口”,如各种软件开发API等;其二则是指人与软件之间的交互界面。

接口设计一般出现在软件开发的概要设计阶段,概要设计要根据需求划分模块,而模块之间的联系就是通过定义接口实现的。例如有模块A和模块B,两者互相不知道对方实现的细节,当模块A要用到模块B中的功能时,就要使用模块B提供的外部接口(接口可以理解为一些功能函数的原型,包括函数名、参数列表和返回值);同样,模块A内可以定义内部接口,供模块A内部的函数调用。当各个模块中的接口完全设计好并通过评审后,各个模块就可以进行独立的详细设计及编码了。下面将详细地讲解接口设计的六大原则内容。

1.6.1 单一职责原则

单一职责原则(Single Responsibility Principle),简称SRP。就一个类而言,应该仅有一个引起它变化的原因。要避免一个类实现多个功能,否则当发生更改时会影响其他功能而致使复用成为不可能。使用的时候应该根据实际业务情况而定。实际使用时,类很难做到职责单一,但是接口的职责应该尽量单一。

1.6.2 依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)。程序设计应该依赖抽象接口,而不应该依赖具体实现,即为接口编程思想。接口是稳定的,实现是不稳定的,一旦接口确定,就不应该再进行修改了。根据接口的实现,可以根据具体问题和情况,采用不同的手段去实现。

依赖倒置原则的定义需注意以下3点。

(1)高层模块不应该依赖低层模块,两者都应该依赖其抽象。

(2)抽象不应该依赖细节。

(3)细节应该依赖抽象。

依赖的3种写法如下。

(1)构造函数传递依赖对象。

(2)Setter方法传递依赖对象。

(3)接口声明依赖对象。

最佳实践:

(1)每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备。

(2)任何类都不应该从具体类派生。

(3)尽量不要覆写基类的方法。

(4)结合里氏替换原则使用。

(5)变量的表面类型应尽量是接口或抽象类。

1.6.3 迪米特法则

迪米特法则(Law of Demeter,LOD)又称最少知识原则(Least Knowledge Principle,简称LKP)。它表示一个实体应当尽可能少地与其他实体之间发生相互作用。

低耦合要求:

(1)朋友类是一种出现在成员变量、方法的输入和输出参数中的类。方法体内部的类不属于朋友类。

(2)迪米特法则要求类“内敛”一点,尽量不要对外公布太多的public方法和非静态的public变量,多使用private、protected、package-private等访问权限。

(3)如果一个方法放在本类中,既不增加类之间的关系,也对本类不产生负面影响,就放置在本类中。

(4)谨慎使用Serializable。

1.6.4 里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)。子类对象能够替换其父类对象被调用,即在程序中,任何调用父类对象实现的功能都可以调用子类对象来替换。

所有引用父类的位置必须能透明地使用其子类的对象,里氏替换原则为确保子类良好地继承父类定义了以下规范。

(1)子类必须完全实现父类的方法。

(2)子类可以有自己的“个性”(属性和方法)。

(3)覆写或实现父类的方法时输出结果可以被缩小。

(4)覆盖或实现父类的方法时输入参数可以被放大。

提示:在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。

1.6.5 接口隔离原则

接口,这里是指用interface关键字定义的接口,使用多个隔离接口比使用单个接口要好。经常提到的降低耦合、降低依赖主要也是通过接口隔离原则来达到的。

接口隔离原则的定义需注意以下两点。

(1)客户端不应该依赖它不需要的接口。

(2)类之间的依赖关系应该建立在最小的接口上。

概括地说,即建立单一接口,不要建立臃肿、庞大的接口。通俗来讲,接口尽量细化,同时接口中的方法尽量少。

保障接口的纯洁性有以下4点需要注意。

(1)接口要尽量小。

(2)定制服务。

(3)接口要高内聚。

(4)接口的设计是有限度的。

最佳实践:

(1)一个接口只服务于一个子模块或业务逻辑。

(2)通过业务逻辑压缩接口中的public方法,尽量让接口简单、代码不繁杂。

(3)对于已经被污染的接口,应尽量去修改。若变更的风险较大,则采用适配器模式进行转化处理。

(4)了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,设计时不要盲从或跟从别人的设计,而要根据业务逻辑进行较佳的接口设计。

1.6.6 开闭原则

开闭原则表示的是程序的设计应该不约束扩展(即扩展开放),但又不能修改已有功能(即修改关闭)。

软件实体包括以下几个部分。

(1)项目和软件产品中按照一定的逻辑规则划分的模块。

(2)抽象和类。

(3)方法。

变化的3种类型如下。

(1)逻辑变化。

(2)可见视图变化。

(3)子模块变化。