-->

后端面试38讲_17_15丨软件设计的接口隔离原则如何对类的调用者隐藏类的公有方法

你好,我是李智慧。

我在阿里巴巴工作的时候呢,曾经负责开发一个统一缓冲服务。

这个服务要求能够根据远程配置中心的配置信息,在运行期动态更改缓冲的配置可能是将本地缓冲更改为远程缓冲,也可能是更改远程缓冲服务器集群的IP地址列表,从而改变应用程序使用的缓冲服务。

这就要求缓冲服务的客户端SBK必须支持用心期配置更新,而配置更新又会直接影响缓冲数据的操作。

于是就设计出这样一个缓冲服务clinter类。

这个缓冲服务clint类的方法主要包含两两部分,分一个部分呢是缓冲服务方法。

Get put delete这些这些方法是面向调用者的。

另一部分呢是配置更新方法,rebuild这个方法主要是给远程配置中心调用的。

但是这里有这样一个问题,catch类的调用者如果看到review的方法错误,调用了这个方法,就可能导致cathe连接被错误重置,无法正常使用catch服务。

所以必须要将review的方法向缓冲服务的调用者隐藏,而只对远程配置中心的本地代理开放这个方法。

但是review的方法是一个public方法,如何对类的调用者隐藏类的公有方法。

我们可以使用接口隔离原则解决这个问题。

接口隔离原则说不应该强迫用户依赖他们不需要的方法。

那么如果强迫用户依赖他们不需要的方法会导致什么后果呢?所以用户可以看到这些,他们不需要也不理解的方法,这样无疑会增加使用的方法。

如果错误的调用了这些方法,就会产生bug.二来,如果这些方法因为某种原因需要更改,虽然不需要,但是依赖这些方法的用户程序也必须要做出更改。

这是一种不必要的耦合。

但是呢如果一个类的几个方法之间,本来就是互相关联的,就像我开头举的那个缓冲cleaned SDK的例子,review的方法必须要在catch类里面。

这种情况下,如何做到不强迫用户依赖他们不需要的方法呢?我们先看一个简单的例子,modem类定义了四个方法,拨号打al挂断,help up发送、send和接受receive.这四个方法互相存在关联,需要定义在一个类别。

但是对调用者来说,某些方法可能完全不需要,也不应该看到,比如拨号打ile和挂断。

Help up.这两个方法是属于专门的网络连接程序的,通过网络连接程序进行拨号上网或者挂断网络。

而一般使用网络的程序,比如网络游戏或者上网浏览器,只需要调用stand和receive发送和接收数据就可以了。

强迫只需要上网的程序,就会他们不需要的拨号与挂断方法,只会导致不必要的耦合带来潜在的系统异常。

比如在上网浏览器中不小心调用了help up方法,就会导致整个机器断网,其他程序都不能够连接网络。

这显然不是系统想要的。

这种问题的解决方法呢就是通过接口进行方法隔离。

Model类实现两个接口data千到接口和connection接口。

Data千到接口都为暴露send和receive方法。

这个接口呢只负责网络数据的发送和接收网络游戏或者网络浏览器,而依赖这个接口进行网络数据传输。

这些应用程序不需要依赖他们不需要的dial和暗hap方法,对应用开发者更加友好,也不会因为错误的调用而引发程序bug.而网络管理程序则可以依赖connection接口提供显示的UI,让用户拨号上网或者挂断网络进行网络连接管理。

通过使用接口隔离原则,我们呢就可以将一个实现类的不同bug包装在不同的接口中都为暴露应用程序,只需要依赖他们需要的方法,而不会看到不需要的方法。

我们再看一个使用接口隔离原则优化的例子。

假设我们有一个门door对象,这个door对象可以锁上,可以检锁,还可以判断门是否打开。

现在我们需要一个timer的door,一个有定时功能的door,如果门开着的时候超过预定时间就会自动锁门。

我们已经有一个类timer和一个接口,timer client timer cent ent可以retient注册,调用rejest的方法,设置超时时间。

当超时时间到就会调用time clent的time out方法。

那么我们如何利用现有的timer和timer clent将将到改造成一个具有超时自动锁门的timer的道能比较容易,而且直观的方法就是修改类到实现dotimer clelient接口。

这样到即有了一个time out方法,直接将的注册给timer.当超时的时候,timer调用的dotime out的方法,在的dotime out方法里面调用log方法,就可以实现超时自动锁门的操作。

这个方法简单直接,也能够实现需求,但是问题在于让dog多了一个time out方法。

如果这个类想要复用到其他地方,那么所有使用的程序都不得不依赖一个可能根本用不着的方法,同时的职责也变得复杂,违反了单一职责的原则,维护会变得更加困难,这样的设计显然是有问题的。

要想解决这些问题啊,就应该遵循接口隔离原则。

实际上呢,这里有两个互相独立的接口,一个接口是timer, clent用来提供timer进行超时控制。

另一个接口是door用来控制门的操作。

虽然超时是锁门的操作是一个完整的动作,但是我们依然可以使用接口将其隔离。

一种方法呢是通过委托进行接口隔离,具体方式就是增加一个适配器。

Door timer adapt这个适配器继承timer client接口,实现time out方法,并将自己注册给timer适配。

配在自己的timer out的方法里,调用door的方法,实现超时锁门的操作。

这种场合使用的适配器可能会比较重,业务逻辑也比较多。

如果超时的时候需要执行较多的逻辑操作,那么适配器的time r的方法就会包含很多的业务逻辑,超出了适配器的职责范围。

而且,如果这些逻辑操作还需要使用doll的内部状态,可能会迫使doll做出一些修改。

接口隔离更典型的方法是使用多重继承。

跟前面的model例子一样,timer doll同时实现timer的接口和基基cledol类,在time dodoor中实现time out的方法,并注册到timer定时器中,这样的程序就不需要被迫依赖dotime out方法。

Timer也不会看到的方法,程序更加整洁,易于复用。

我们再来看一个接口隔离原则,在迭代器设计模式中的应用,java的数据结构、容器类可以通过for循环直接进行遍历。

事实上,这种for语法结构并不是标准的java for语法。

你在文稿中可以看到,在实现遍历时标准的for语法是采样的。

之所以可以写成文章中这种简单的形式,就是因为java提供的语法糖,java五以后版本对所有实现的ita BO接口的类,而可以使用这种简化的for循环进行遍历。

而我们上面例子子的relist也是实现了这个接口。

在java五以前,每种容器的遍历方法都不相同。

在java五以后呢,我们可以统一使用这种简化的遍历语法,实现对容器的遍历。

而实现这一特性,主要就是在于java五通过ita rebel接口,将容器的遍历方法将容器的其他操作中割离了出来,让java可以针对这个接口进行优化,提供更加便利解解统统的语法。

我们再回到开透那个例子,如何让缓冲类的使用者看不到缓冲重构的方法,避免不必要的依赖和方法的误用呢?答案就是使用接口隔离原则,通过多重继承的方法进行接口隔离catch实现内它bucathe时实现ccathe接口和catch managage able务口中ccch接口提供标准的catch服务方法,应用程序只需要依赖该接口。

而catch manager able接口则都为暴露review的方法使远程配置服务。

可以通过本地代理调用这个方法,在运行器远程调整缓冲服务配置,使系统无需重新部署,就可以热更新。

当一个类比较大的时候,如果该类的不同调用者被迫依赖类的所有方法,就可能会产生不必要的耦合,对这个类的改动也可能会影响到它的不同。

调用者引起误用,导致对象被破坏,引发bug.使用接口隔离原则就是定义多个接口,不同调用者依赖不同的接口,只看到自己需要的方法,而实现类则实现这些接口,通过多个接口将类内部的不同方法隔离开来。

在你的开发实践中,你看到过哪些地方?使用了接口隔离原则,你自己开发的代码,有哪些地方可以用接口隔离原则优化。

欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友,或者同事一起交流一下。