精华帖 (0) :: 良好帖 (7) :: 新手帖 (4) :: 隐藏帖 (10)
|
|
---|---|
作者 | 正文 |
发表时间:2009-11-25
最后修改:2009-11-28
蛰伏N久,我cbf4life又回来了! 先奉上一小篇,欢迎拍砖。
美女何其多,观点各不同我们举例来说明接口隔离原则到底对我们提出了什么要求。现在男生对小姑娘的称呼,使用频率最高的应该是“美女”了吧,你在大街上叫一声:“嗨,美女!”,估计10个有8个回头,其中包括那位著名的如花。美女的标准参差不齐,首先就需要定义一下什么是美女:首先要面貌好看,其次是身材要窈窕,然后要有气质,当然了,这三者各人的排列顺序不一样,总之要成为一名美女就必须具备:面貌、身材和气质,我们用类图体现一下星探(当然,你也可以把你自己想象成星探)找美女的过程,如下图所示。 星探寻找美女的类图 定义了一个IPettyGirl接口,声明所有的美女都应该有goodLooking、niceFigure和greatTemperament,然后又定义了一个抽象类AbstractSearcher,其作用就是搜索美女并显示其信息,只要美女都按照这个规范定义,Searcher(星探)就轻松多了,美女类的实现代码如下所示。
public interface IPettyGirl { //要有姣好的面孔 public void goodLooking(); //要有好身材 public void niceFigure(); //要有气质 public void greatTemperament(); } 美女的标准定义完毕,具体的美女实现类如下所示。
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 + "---身材非常棒!"); } } 通过三个方法,把对美女的要求都定义出来了,按照这个标准,如花姑娘是排除在美女标准之外了。有美女,就有搜索美女的星探,其具体实现美女如下所示。
public abstract class AbstractSearcher { protected IPettyGirl pettyGirl; public AbstractSearcher(IPettyGirl _pettyGirl){ this.pettyGirl = _pettyGirl; } //搜索美女,列出美女信息 public abstract void show(); }
星探的实现类就比较简单了,其源代码如下示。
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(); } }
场景中的两个角色美女和星探都已经出现了,需要写一个场景类来串联起各个角色,场景类如下所示。
public class Client { //搜索并展示美女信息 public static void main(String[] args) { //定义一个美女 IPettyGirl yanYan = new PettyGirl("嫣嫣"); AbstractSearcher searcher = new Searcher(yanYan); searcher.show(); } } 星探搜索美女的运行结果如下所示。 --------美女的信息如下:--------------- 嫣嫣---脸蛋很漂亮! 嫣嫣---身材非常棒! 嫣嫣---气质非常好! 星探寻找美女的程序我们就开发完毕了,运行结果也正确。我们回头来想想这个程序有没有问题,思考一下IPettyGirl这个接口,这个接口是否做到了最优化设计,答案是没有,还可以对接口进行优化。
========================== 2009年11月27日 更新 ==========================
我们的审美观点都在改变,美女的定义也在变化。一千多年前的唐朝杨贵妃如果活在现代这个年代非羞愧而死不可,为什么?胖呀!但是胖并不影响她入选中国四大美女,说明当时的审美观与现在是有差异的。当然,随着时代的发展我们的审美观也在变化,突然有一天,也不用突然有一天,就现在,你发现有一个女孩,脸蛋不怎么样,身材也一般般,但是气质非常好,我相信大部分人都会把这样的女孩叫美女,审美素质提升了,就产生了气质型美女,但是我们接口却定义了美女必须是三者都具备,按照这个标准,气质型美女就不能算美女,那怎么办?可能你要说了,我重新扩展一个美女类,只实现greatTemperament方法,其他两个方法置空,什么都不写,不就可以了吗?聪明,但是行不通!为什么呢?星探AbstractSearcher依赖的是IPettyGirl接口,它有三个方法,你只实现了两个方法,星探的方法是不是要修改?我们上面的程序打印出来的信息少了两条,还让星探怎么去辨别是不是美女呢? 分析到这里,我们发现接口IPettyGirl的设计是有缺陷地,过于庞大了,容纳了一些可变的因素,根据接口隔离原则,星探AbstractSearcher应该依赖于具有部分特质的女孩子,而我们却把这些特质都封装了起来,放到了一个接口中,封装过渡了!问题找到了,我们重新设计一下类图,修改后的类图如下所示。
修改后的星探寻找美女类图 把原IPettyGirl接口拆分为两个接口,一种是外形美的美女IGoodBodyGirl,这类美女的特点就是脸蛋和身材极棒,超一流,但是没有审美素质,比如随地吐痰,出口就是KAO、CAO之类的,文化程度比较低;另外一种是气质美的美女IGreatTemperamentGirl,谈吐和修养都非常高。我们把一个比较臃肿的接口拆分成了两个专门的接口,灵活性提高了,可维护性也增加了,不管以后是要外形美的美女还是气质美的美女都可以轻松地通过PettyGirl定义。两种类型的美女定义如下所示。
public interface IGoodBodyGirl { //要有姣好的面孔 public void goodLooking(); //要有好身材 public void niceFigure(); } public interface IGreatTemperamentGirl { //要有气质 public void greatTemperament(); } 按照脸蛋、身材、气质都具备才算美女,实现类实现两个接口,如下所示。
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依赖两个专用的接口比依赖一个综合的接口要灵活。接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
=========================2009年11月28日 ==============================
保证接口的纯洁性接口隔离原则是对接口进行规范约束,其包含以下四层含义: q 接口尽量要小 这是接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则,什么意思呢?我们在单一职责原则中提到一个IPhone的例子,在这里例子中我们使用单一职责原则把两个职责分解到两个接口中,类图如下所示。 电话类图 仔细分析一下IConnectManager接口是否还可以再继续拆分下去,挂电话有两种方式:一种是正常的电话挂断,一种是电话异常挂机,比如突然没电了,通讯当然就断了,这两种方式的处理应该是不同的,为什么呢?正常挂电话,对方接受到挂机信号,计费系统也就停止计费了,那手机没电了这种方式就不同了,它是信号丢失了,中继服务器检查到了,然后通知计费系统停止计费,否则你的费用不是要疯狂的增长了吗? 思考到这里,我们是不是就要动手把IConnectManager接口拆封成两个,一个接口是负责连接,一个接口是负责挂电话?是要这样做吗?且慢,让我们再思考一下,如果拆分了,那就不符合单一职责原则了,因为从业务逻辑上来讲,通讯的建立和关闭已经是最小的业务单位了,再细分下去就是对业务或是协议(其他业务逻辑)的拆解了,想想看,一个电话要关心3G协议,要考虑中继服务器,等等,这个电话还怎么设计得出来呢?从业务层次来看,这样的设计就是一个失败的设计。一个原则要拆,一个原则又不要拆,那怎么办?好办,根据接口隔离原则拆分接口时,必须首先满足单一职责原则。 q 接口要高内聚 什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。比如你告诉下属“到奥巴马的办公室偷一个XXX文件”,然后听到下属用坚定的口吻回答你:“是,保证完成任务!”一个月后,你的下属还真地把XXX文件放到你的办公桌上了,这种不讲任何条件、立刻完成任务的行为就是高内聚的表现。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。 q 定制服务 一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口(并不一定就是Java中定义的Interface,也可能是一个类或单纯的数据交换),我们设计时就需要为各个访问者(也就客户端)定制服务,什么是定制服务?你到商场买衣服,找到符合自己身体的尺码的衣服就成了,基本上就不会有太大差别,可能是前松后紧,晚上睡不着觉之类的不太合适,但是好歹也是个衣服,能穿。如果你到裁缝店里做衣服会是什么样子呢?裁缝会帮你量腰围、胸围、肩宽等等,然后做出一件衣服,这件衣服时专为你的身材而制定的,肯定非常符合你,那这就是定制服务,单独为一个个体提供优良的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的接口要采用定制服务。采用定制服务就必然有一个要求:只提供访问者需要的方法,这是什么意思?我们举个例子来说明,比如我们做了一个图书管理系统,其中有一个查询接口,方便管理员查询图书,其类图如下所示。
图书查询类图 在接口中定义了多个查询方法,分别可以按照作者、标题、出版社、分类进行查询,最后还提供了混合查询方式。程序写好了,投产上线了,突然有一天发现系统速度非常慢,然后就开始痛苦地分析,最终发现是访问接口中的complexSearch(Map map)方法并发量太大,导致应用服务器性能下降,然后继续跟踪下去发现这些查询都是从公网上发起的,进一步分析,找到问题:提供给公网(公网项目是另外一个项目组开发的)的查询接口和提供给系统内管理人员的接口是相同的,都是IBookSearcher接口,但是权限不同,系统管理人员可以通过接口的complexSearch方法查询到所有的书籍,而公网的这个方法是被限制的,不返回任何值,在设计时通过口头约束,这个方法是不可被调用的,但是由于公网项目组的疏忽,这个方法还是公布了出去,虽然不能返回结果,但是还是引起了应用服务器的性能巨慢的情况发生,这就是一个臃肿接口引起性能故障的案例。 问题找到了,就需要把这个接口进行重构,将IBookSearcher拆分为两个接口,分别为两个模块提供定制服务,修改后的类图如下所示。
修改后的图书查询类图 提供给管理人员的实现类同时实现了ISimpleBookSearcher和IComplexBookSearcher两个接口,原有程序不用做任何改变,而提供给公网的接口变为ISimpleBookSearcher,只允许进行简单的查询,单独为其定制服务,减少可能引起的风险。 q 接口设计是有限度的 接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度,这个“度”如何来判断的呢?根据经验和常识判断,没有一个固化或可测量的标准。
(未完,待续)
也可以到我的博客上畅所欲言(可以匿名哦,随你怎么轰炸了):http://hi.baidu.com/cbf4life
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-11-25
还不到12点,怎么就没人了?
抢个沙发,睡着等后面的文章,哈哈! |
|
返回顶楼 | |
发表时间:2009-11-26
你是翻译官网的东西 还是自己的想法?
|
|
返回顶楼 | |
发表时间:2009-11-26
纯洁只有一次,不纯洁才有通用性
|
|
返回顶楼 | |
发表时间:2009-11-26
jinsv_eye 写道 纯洁只有一次,不纯洁才有通用性
纯洁为撒只有一次?这句话貌似其深意有待挖掘~~ |
|
返回顶楼 | |
发表时间:2009-11-26
whaosoft 写道 你是翻译官网的东西 还是自己的想法?
这个有官网? 给个地址看看,世界上有这样的巧合。 |
|
返回顶楼 | |
发表时间:2009-11-26
jinsv_eye 写道 纯洁只有一次,不纯洁才有通用性
从程序员的观点来看,非常正确,但是站的稍微高一点的层次去看,基本不可行,比如一个接口就一个execute方法,所有的对象都可以实现该接口,非常通用,但是需要配置大量的文档说明才能表达清楚你的意图,程序首先是给人看的,然后才是被机器阅读的。 |
|
返回顶楼 | |
发表时间:2009-11-26
接口更多的阐述一种“能力”。
脸蛋 漂亮-不漂亮 身材 好-不好 气质 好-不好 按照排列组合,6种实现。 如果在有新的选项呢,比如胸大不大,屁股翘不翘。成千上万的子类产生了~ 不如定义 interface goodlook,nicefigur等接口 而PrettyGirl作为抽象类有选择的实现上述接口 我猜楼主是想说策略模式~ |
|
返回顶楼 | |
发表时间:2009-11-26
LZ说的接口隔离是不是就是针对特定用户产生特定功能的接口类。这是不是有点背离代码复用的思想。不知道LZ可否提供一些参看网站或资料。
|
|
返回顶楼 | |
发表时间:2009-11-26
我的理解是,其实就像装修一间房间,房间依赖 窗,门,桌
装修房间需要窗,需要门,需要桌。 但是窗,门,桌有很多种表现形式, 只用窗来说下,窗有很多中选择是因为对“窗”这个概念存在不同实现,窗的概念就是单一接口,以此类推,门,桌同样存在这样情况。 回到装修房子的概念上来说他依赖 窗 门 桌子等概念的单一接口。 总的设计目的也就是为了达到组件的轻松装卸化,即极大地应该是极致的松耦合性 |
|
返回顶楼 | |