后端面试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.使用接口隔离原则就是定义多个接口,不同调用者依赖不同的接口,只看到自己需要的方法,而实现类则实现这些接口,通过多个接口将类内部的不同方法隔离开来。
在你的开发实践中,你看到过哪些地方?使用了接口隔离原则,你自己开发的代码,有哪些地方可以用接口隔离原则优化。
欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友,或者同事一起交流一下。