论坛首页 Java企业应用论坛

俺又回来了,先奉上一篇——接口隔离原则_20091128更新

浏览 9918 次
精华帖 (0) :: 良好帖 (7) :: 新手帖 (4) :: 隐藏帖 (10)
作者 正文
   发表时间:2009-11-25   最后修改:2009-11-28

蛰伏N久,我cbf4life又回来了!  先奉上一小篇,欢迎拍砖。

接口隔离原则的定义

    在讲接口隔离原则之前,先明确一下我们的主角——接口。接口分为两种:
        实例接口(Object Interface),在Java中声明一个类,然后用new关键字产生的一个实例,它是对一个类型的事物的描述,这是一种接口,比如你定义Person这个类,然后使用Person zhangSan = new Person()产生了一个实例,这个实例要遵从的标准就是Person这个类,Person类就是zhangSan的接口,疑惑?看不懂?不要紧,那是因为让Java语言浸染的时间太长了,只要知道从这个角度来看,Java中的类也是一种接口;
        类接口(Class Interface),Java中经常使用的interface关键字定义的接口。
    主角已经定义清楚了,那什么是隔离呢?它有两种定义,如下所示:
        “Clients should not be forced to depend upon interfaces that they don't use”——客户端不应该依赖它不需用的接口。
        “The dependency of one class to another one should depend on the smallest possible interface”——类间的依赖关系应该建立在最小的接口上。
      新事物的定义一般都比较难理解,晦涩难懂是正常的。我们把这两个定义剖析一下,先说第一种定义:“客户端不应该依赖它不需要接口”,那依赖什么?依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性;再看第二个定义:“类间的依赖关系应该建立在最小的接口上”,它要求是最小的接口,也是要求接口细化,接口纯洁,与第一个定义如出一辙,只是一个事物的两种不同描述。
      我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。看到这里大家有可能要疑惑了,这与单一职责原则不是相同的吗?错,接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”,专门的接口指什么?就是指提供给每个模块都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。

 

 

 

 

 美女何其多,观点各不同

     我们举例来说明接口隔离原则到底对我们提出了什么要求。现在男生对小姑娘的称呼,使用频率最高的应该是“美女”了吧,你在大街上叫一声:“嗨,美女!,估计10个有8个回头,其中包括那位著名的如花。美女的标准参差不齐,首先就需要定义一下什么是美女:首先要面貌好看,其次是身材要窈窕,然后要有气质,当然了,这三者各人的排列顺序不一样,总之要成为一名美女就必须具备:面貌、身材和气质,我们用类图体现一下星探(当然,你也可以把你自己想象成星探)找美女的过程,如下图所示。

 星探寻找美女的类图

      定义了一个IPettyGirl接口,声明所有的美女都应该有goodLookingniceFiguregreatTemperament,然后又定义了一个抽象类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,这类美女的特点就是脸蛋和身材极棒,超一流,但是没有审美素质,比如随地吐痰,出口就是KAOCAO之类的,文化程度比较低;另外一种是气质美的美女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拆分为两个接口,分别为两个模块提供定制服务,修改后的类图如下所示。

 修改后的图书查询类图

      提供给管理人员的实现类同时实现了ISimpleBookSearcherIComplexBookSearcher两个接口,原有程序不用做任何改变,而提供给公网的接口变为ISimpleBookSearcher,只允许进行简单的查询,单独为其定制服务,减少可能引起的风险。

      q  接口设计是有限度的

      接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度,这个“度”如何来判断的呢?根据经验和常识判断,没有一个固化或可测量的标准。

 

(未完,待续)

 

 

也可以到我的博客上畅所欲言(可以匿名哦,随你怎么轰炸了):http://hi.baidu.com/cbf4life

 

 

  • 大小: 5.2 KB
  • 大小: 6.7 KB
  • 大小: 6.1 KB
  • 大小: 3.8 KB
  • 大小: 4.8 KB
   发表时间:2009-11-25  
还不到12点,怎么就没人了?

抢个沙发,睡着等后面的文章,哈哈!
0 请登录后投票
   发表时间:2009-11-26  
你是翻译官网的东西 还是自己的想法?
0 请登录后投票
   发表时间:2009-11-26  
纯洁只有一次,不纯洁才有通用性
0 请登录后投票
   发表时间:2009-11-26  
jinsv_eye 写道
纯洁只有一次,不纯洁才有通用性


纯洁为撒只有一次?这句话貌似其深意有待挖掘~~ 
0 请登录后投票
   发表时间:2009-11-26  
whaosoft 写道
你是翻译官网的东西 还是自己的想法?



这个有官网? 给个地址看看,世界上有这样的巧合。
0 请登录后投票
   发表时间:2009-11-26  
jinsv_eye 写道
纯洁只有一次,不纯洁才有通用性


  从程序员的观点来看,非常正确,但是站的稍微高一点的层次去看,基本不可行,比如一个接口就一个execute方法,所有的对象都可以实现该接口,非常通用,但是需要配置大量的文档说明才能表达清楚你的意图,程序首先是给人看的,然后才是被机器阅读的。
0 请登录后投票
   发表时间:2009-11-26  
接口更多的阐述一种“能力”。

脸蛋 漂亮-不漂亮

身材 好-不好

气质 好-不好

按照排列组合,6种实现。

如果在有新的选项呢,比如胸大不大,屁股翘不翘。成千上万的子类产生了~

不如定义 interface goodlook,nicefigur等接口

而PrettyGirl作为抽象类有选择的实现上述接口

我猜楼主是想说策略模式~
0 请登录后投票
   发表时间:2009-11-26  
LZ说的接口隔离是不是就是针对特定用户产生特定功能的接口类。这是不是有点背离代码复用的思想。不知道LZ可否提供一些参看网站或资料。
0 请登录后投票
   发表时间:2009-11-26  
我的理解是,其实就像装修一间房间,房间依赖 窗,门,桌

装修房间需要窗,需要门,需要桌。

但是窗,门,桌有很多种表现形式,
只用窗来说下,窗有很多中选择是因为对“窗”这个概念存在不同实现,窗的概念就是单一接口,以此类推,门,桌同样存在这样情况。

回到装修房子的概念上来说他依赖 窗 门 桌子等概念的单一接口。

总的设计目的也就是为了达到组件的轻松装卸化,即极大地应该是极致的松耦合性
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics