我们举例来说明接口隔离原则到底对我们提出了什么要求。现在男生对小姑娘的称呼,使用频率最高的应该是“美女”了吧,你在大街上叫一声:“嗨,美女!”估计10个有8个回头,其中包括那位著名的如花。美女的标准各不相同,首先就需要定义一下什么是美女:首先要面貌好看,其次是身材要窈窕,然后要有气质,当然了,这三者各人的排列顺序不一样,总之要成为一名美女就必须具备:面貌、身材和气质,我们用类图体现一下星探(当然,你也可以把自己想象成星探)找美女的过程,如图4-1所示。
图4-1 星探寻找美女的类图
定义了一个IPettyGirl接口,声明所有的美女都应该有goodLooking、niceFigure和great-Temperament,然后又定义了一个抽象类AbstractSearcher,其作用就是搜索美女并显示其信息,只要美女都按照这个规范定义,Searcher(星探)就轻松多了,美女类的实现如代码清单4-1所示。
代码清单4-1 美女类
public interface IPettyGirl { //要有姣好的面孔 public void goodLooking; //要有好身材 public void niceFigure; //要有气质 public void greatTemperament;}
美女的标准定义完毕,具体的美女实现类如代码清单4-2所示。
代码清单4-2 美女实现类
public class PettyGirl implements IPettyGirl { private String name; //美女都有名字 public PettyGirl(String _name){ this.name=_name; } //脸蛋漂亮 public void goodLooking { System.out.println(this.name + /"---脸蛋很漂亮!/"); } //气质要好 public void greatTemperament { System.out.println(this.name + /"---气质非常好!/"); } //身材要好 public void niceFigure { System.out.println(this.name + /"---身材非常棒!/"); }}
通过三个方法,把对美女的要求都定义出来了,按照这个标准,如花姑娘被排除在美女标准之外了。有美女,就有搜索美女的星探,其具体实现如代码清单4-3所示。
代码清单4-3 星探抽象类源代码
public abstract class AbstractSearcher { protected IPettyGirl pettyGirl; public AbstractSearcher(IPettyGirl _pettyGirl){ this.pettyGirl = _pettyGirl; } //搜索美女,列出美女信息 public abstract void show;}
星探的实现类就比较简单了,其源代码如代码清单4-4所示。
代码清单4-4 星探类
public class Searcher extends AbstractSearcher{ public Searcher(IPettyGirl _pettyGirl){ super(_pettyGirl); } //展示美女的信息 public void show{ System.out.println(/"--------美女的信息如下:---------------/"); //展示面容 super.pettyGirl.goodLooking; //展示身材 super.pettyGirl.niceFigure; //展示气质 super.pettyGirl.greatTemperament; }}
场景中的两个角色美女和星探都已经出现了,需要写一个场景类来串联起各个角色,场景类的实现如代码清单4-5所示。
代码清单4-5 场景类
public class Client { //搜索并展示美女信息 public static void main(String args) { //定义一个美女 IPettyGirl yanYan = new PettyGirl(/"嫣嫣/"); AbstractSearcher searcher = new Searcher(yanYan); searcher.show; }}
星探搜索美女的运行结果如下所示:
--------美女的信息如下:---------------
嫣嫣---脸蛋很漂亮!
嫣嫣---身材非常棒!
嫣嫣---气质非常好!
星探寻找美女的程序开发完毕了,运行结果也正确。我们回头来想想这个程序有没有问题,思考一下IPettyGirl这个接口,这个接口是否做到了最优化设计?答案是没有,还可以对接口进行优化。
我们的审美观点都在改变,美女的定义也在变化。唐朝的杨贵妃如果活在现在这个年代非羞愧而死不可,为什么?胖呀!但是胖并不影响她入选中国四大美女,说明当时的审美观与现在是有差异的。当然,随着时代的发展我们的审美观也在变化,当你发现有一个女孩,脸蛋不怎么样,身材也一般般,但是气质非常好,我相信大部分人都会把这样的女孩叫美女,审美素质提升了,就产生了气质型美女,但是我们的接口却定义了美女必须是三者都具备,按照这个标准,气质型美女就不能算美女,那怎么办?可能你要说了,我重新扩展一个美女类,只实现greatTemperament方法,其他两个方法置空,什么都不写,不就可以了吗?聪明,但是行不通!为什么呢?星探AbstractSearcher依赖的是IPettyGirl接口,它有三个方法,你只实现了两个方法,星探的方法是不是要修改?我们上面的程序打印出来的信息少了两条,还让星探怎么去辨别是不是美女呢?
分析到这里,我们发现接口IPettyGirl的设计是有缺陷的,过于庞大了,容纳了一些可变的因素,根据接口隔离原则,星探AbstractSearcher应该依赖于具有部分特质的女孩子,而我们却把这些特质都封装了起来,放到了一个接口中,封装过度了!问题找到了,我们重新设计一下类图,修改后的类图如图4-2所示。
把原IPettyGirl接口拆分为两个接口,一种是外形美的美女IGoodBodyGirl,这类美女的特点就是脸蛋和身材极棒,超一流,但是没有审美素质,比如随地吐痰,文化程度比较低;另外一种是气质美的美女IGreatTemperamentGirl,谈吐和修养都非常高。我们把一个比较臃肿的接口拆分成了两个专门的接口,灵活性提高了,可维护性也增加了,不管以后是要外形美的美女还是气质美的美女都可以轻松地通过PettyGirl定义。两种类型的美女定义如代码清单4-6所示。
图4-2 修改后的星探寻找美女类图
代码清单4-6 两种类型的美女定义
public interface IGoodBodyGirl { //要有姣好的面孔 public void goodLooking; //要有好身材 public void niceFigure;}public interface IGreatTemperamentGirl { //要有气质 public void greatTemperament;}
按照脸蛋、身材、气质都具备才算美女,实现类实现两个接口,如代码清单4-7所示。
代码清单4-7 最标准的美女
public class PettyGirl implements IGoodBodyGirl,IGreatTemperamentGirl { private String name; //美女都有名字 public PettyGirl(String _name){ this.name=_name; } //脸蛋漂亮 public void goodLooking { System.out.println(this.name + /"---脸蛋很漂亮!/"); } //气质要好 public void greatTemperament { System.out.println(this.name + /"---气质非常好!/"); } //身材要好 public void niceFigure { System.out.println(this.name + /"---身材非常棒!/"); }}
通过这样的重构以后,不管以后是要气质美女还是要外形美女,都可以保持接口的稳定。当然,你可能要说了,以后可能审美观点再发生改变,只有脸蛋好看就是美女,那这个IGoodBody接口还是要修改的呀,确实是,但是设计是有限度的,不能无限地考虑未来的变更情况,否则就会陷入设计的泥潭中而不能自拔。
以上把一个臃肿的接口变更为两个独立的接口所依赖的原则就是接口隔离原则,让星探AbstractSearcher依赖两个专用的接口比依赖一个综合的接口要灵活。接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。