设计模式六大原则之--接口隔离原则(ISP)
2014-04-18 11:24
337 查看
1.接口隔离原则:(Interface Segregation Principle, ISP)
定义:Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)。或
The dependcy of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)。或
使用多个专门的接口比使用单一的总接口要好。
2.理解:
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。 在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
单一职责与接口隔离的区别:
单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节; 而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
3.问题由来:
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类C来说不是最小接口,则类B和类D必须去实现它们不需要的方法。[解决方案]将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
4.使用ISP的好处:
原则意义上的好处:接口如果能够保持粒度够小,就能保证它足够稳定,正如单一职责原则所飘洋过海榜的那样。(举例:多个专门的接口就好比采用活字制版,可以随时拼版拆版,既利于修改,又利于文字的重用。而单一的总接口就是雕版的印刷,一旦发现错别字,既难改,又需要整块重新雕刻。)
使用多个专门的接口还能够体现对象的层次,因为我们可以通过接口的继承,实现对总接口的定义。(例如,.NET框架中IList接口的定义。)
如果不采用这样的接口继承方式,而是定义一个总的接口包含上述成员,就无法实现IEnumerable接口、ICollection接口与IList接口成员之间的隔离。假如这个总接口名为IGeneralList,它抹平了IEnumerable接口、ICollection接口与IList接口之间的差别,包含了它们的所有方法。现在,如果我们需要定义一个Hashtable类。根据数据结构的特性,它将无法实现IGeneralList接口。因为Hashtable包含的Add()方法,需要提供键与值,而之前针对ArrayList的Add()方法,则只需要值即可。这意味着两者的接口存在差异。我们需要专门为Hashtable定义一个接口,例如IDictionary,但它却与IGeneralList接口不存在任何关系。正是因为一个总接口的引入,使得我们在可枚举与集合层面上丢失了共同的抽象意义。虽然Hashtable与ArrayList都是可枚举的,也都具备集合特征,它们却不可互换。
如果遵循接口隔离原则,将各自的集合操作功能分解为不同的接口,那么站在ICollection以及IEnumerable的抽象层面上,可以认为ArrayList和Hashtable是相同的对象。在这一抽象层面上,二者是可替换的,如图2-9所示。这样的设计保证了一定程度的重用性与可扩展性。从某种程度来讲,接口隔离原则可以看做是接口层的单一职责原则。
倘若一个类实现了所有的专门接口,从实现上看,它与实现一个总接口的方式并无区别;但站在调用者的角度,不同的接口代表了不同的关注点、不同的职责,甚至是不同的角色。因此,面对需求不同的调用者,这样的类就可以提供一个对应的细粒度接口去匹配。此外,一个庞大的接口不利于我们对其进行测试,因为在为该接口实现Mock或Fake对象 时,需要实现太多的方法。
概括地讲,面向对象设计原则仍然是面向对象思想的体现。例如,单一职责原则与接口隔离原则体现了封装的思想,开放封闭原则体现了对象的封装与多态,而Liskov替换原则是对对象继承的规范,至于依赖倒置原则,则是多态与抽象思想的体现。在充分理解面向对象思想的基础上,掌握基本的设计原则,并能够在项目设计中灵活运用这些原则,就能够改善我们的设计,尤其能够保证可重用性、可维护性与可扩展性等系统的质量属性。这些核心要素与设计原则,就是我们设计的对象法则,它们是理解和掌握设计模式的必备知识。
5.难点:
接口要尽量小(核心定义),但“小”也有限,首先不能违反单一职责原则(接口定义出来是让类来实现的嘛,倘若如此,实现类怎么来SRP?)(去看SRP的7.2节);
接口要高内聚(高内聚:提高接口、类、模块的处理能力,减少对外的交互。例如,不讲任何条件、立刻完成任务的行为就是高内聚的表现),具体到接口隔离原则 ,就是要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本;
定制服务,一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口,在设计时,就需要为各个访问者定制服务(定制服务就是单独为一个个体提供优良的服务:只提供访问者需要的方法),本质也是ISP,按需拆分接口;
接口设计是有限度的,但无固化标准。
6.最佳实践:
一个接口只服务于一个子模块或业务逻辑;
通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“筋骨”,而不是“肥嘟嘟”的一大堆方法;
已经被 污染的接口,要尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
了解环境,拒绝盲从。
7.范例:
7.1 一个反例(接口臃肿),大意来自3.问题
图1 未遵循ISP的设计图
这个图的意思是:类A依赖接口I中的方法1,2,3; 类C依赖接口I中的方法1,4,5; 类B与类D分别是对类A与类C依赖的实现。 对于类B与类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也 必须要实现这些用不到的方法。代码如下:
图2 一个遵循ISP的设计图
对应的设计代码如下:
7.2 一个在需求变化中,才发现接口粒度过大的例子:
星控找美女的过程:
图3 初步的星探找美女图类,美女必须 长得好看、身材好、有气质
但是随着人们审美水品的不断提升,人们对气质美女也产生了很大的认同感,即不太要求长相与身材,这时,新的类图如下:(实为7.1节的演化版)
图4 新类图,如果一开始能做到此,便能防患于未然
转自:/article/2368987.html
定义:Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)。或
The dependcy of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)。或
使用多个专门的接口比使用单一的总接口要好。
2.理解:
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。 在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
单一职责与接口隔离的区别:
单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节; 而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
3.问题由来:
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类C来说不是最小接口,则类B和类D必须去实现它们不需要的方法。[解决方案]将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
4.使用ISP的好处:
原则意义上的好处:接口如果能够保持粒度够小,就能保证它足够稳定,正如单一职责原则所飘洋过海榜的那样。(举例:多个专门的接口就好比采用活字制版,可以随时拼版拆版,既利于修改,又利于文字的重用。而单一的总接口就是雕版的印刷,一旦发现错别字,既难改,又需要整块重新雕刻。)
使用多个专门的接口还能够体现对象的层次,因为我们可以通过接口的继承,实现对总接口的定义。(例如,.NET框架中IList接口的定义。)
public interface IEnumerable { IEnumerator GetEnumerator(); } public interface ICollection : IEnumerable { void CopyTo(Array array, int index); // 其余成员略 } public interface IList : ICollection, IEnumerable { int Add(object value); void Clear(); bool Contains(object value); int IndexOf(object value); void Insert(int index, object value); void Remove(object value); void RemoveAt(int index); // 其余成员略 }
如果不采用这样的接口继承方式,而是定义一个总的接口包含上述成员,就无法实现IEnumerable接口、ICollection接口与IList接口成员之间的隔离。假如这个总接口名为IGeneralList,它抹平了IEnumerable接口、ICollection接口与IList接口之间的差别,包含了它们的所有方法。现在,如果我们需要定义一个Hashtable类。根据数据结构的特性,它将无法实现IGeneralList接口。因为Hashtable包含的Add()方法,需要提供键与值,而之前针对ArrayList的Add()方法,则只需要值即可。这意味着两者的接口存在差异。我们需要专门为Hashtable定义一个接口,例如IDictionary,但它却与IGeneralList接口不存在任何关系。正是因为一个总接口的引入,使得我们在可枚举与集合层面上丢失了共同的抽象意义。虽然Hashtable与ArrayList都是可枚举的,也都具备集合特征,它们却不可互换。
如果遵循接口隔离原则,将各自的集合操作功能分解为不同的接口,那么站在ICollection以及IEnumerable的抽象层面上,可以认为ArrayList和Hashtable是相同的对象。在这一抽象层面上,二者是可替换的,如图2-9所示。这样的设计保证了一定程度的重用性与可扩展性。从某种程度来讲,接口隔离原则可以看做是接口层的单一职责原则。
倘若一个类实现了所有的专门接口,从实现上看,它与实现一个总接口的方式并无区别;但站在调用者的角度,不同的接口代表了不同的关注点、不同的职责,甚至是不同的角色。因此,面对需求不同的调用者,这样的类就可以提供一个对应的细粒度接口去匹配。此外,一个庞大的接口不利于我们对其进行测试,因为在为该接口实现Mock或Fake对象 时,需要实现太多的方法。
概括地讲,面向对象设计原则仍然是面向对象思想的体现。例如,单一职责原则与接口隔离原则体现了封装的思想,开放封闭原则体现了对象的封装与多态,而Liskov替换原则是对对象继承的规范,至于依赖倒置原则,则是多态与抽象思想的体现。在充分理解面向对象思想的基础上,掌握基本的设计原则,并能够在项目设计中灵活运用这些原则,就能够改善我们的设计,尤其能够保证可重用性、可维护性与可扩展性等系统的质量属性。这些核心要素与设计原则,就是我们设计的对象法则,它们是理解和掌握设计模式的必备知识。
5.难点:
接口要尽量小(核心定义),但“小”也有限,首先不能违反单一职责原则(接口定义出来是让类来实现的嘛,倘若如此,实现类怎么来SRP?)(去看SRP的7.2节);
接口要高内聚(高内聚:提高接口、类、模块的处理能力,减少对外的交互。例如,不讲任何条件、立刻完成任务的行为就是高内聚的表现),具体到接口隔离原则 ,就是要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本;
定制服务,一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口,在设计时,就需要为各个访问者定制服务(定制服务就是单独为一个个体提供优良的服务:只提供访问者需要的方法),本质也是ISP,按需拆分接口;
接口设计是有限度的,但无固化标准。
6.最佳实践:
一个接口只服务于一个子模块或业务逻辑;
通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“筋骨”,而不是“肥嘟嘟”的一大堆方法;
已经被 污染的接口,要尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
了解环境,拒绝盲从。
7.范例:
7.1 一个反例(接口臃肿),大意来自3.问题
图1 未遵循ISP的设计图
这个图的意思是:类A依赖接口I中的方法1,2,3; 类C依赖接口I中的方法1,4,5; 类B与类D分别是对类A与类C依赖的实现。 对于类B与类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也 必须要实现这些用不到的方法。代码如下:
interface I { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } class A{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method2(); } public void depend3(I i){ i.method3(); } } class B implements I{ public void method1() { System.out.println("类B实现接口I的方法1"); } public void method2() { System.out.println("类B实现接口I的方法2"); } public void method3() { System.out.println("类B实现接口I的方法3"); } //对于类A来说,method4和method5不是必须的,但是由于接口A中有这两个方法, //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 public void method4() {} public void method5() {} } class C{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method4(); } public void depend3(I i){ i.method5(); } } class D implements I{ public void method1() { System.out.println("类D实现接口I的方法1"); } //对于类C来说,method4和method5不是必须的,但是由于接口A中有这两个方法, //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 public void method2() {} public void method3() {} public void method4() { System.out.println("类D实现接口I的方法4"); } public void method5() { System.out.println("类D实现接口I的方法5"); } } public class Client{ public static void main(String[] args){ A a = new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c = new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); } }可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它们的类有没有用处,实现类中都必须去实现这些方法,这显然是不好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如下所示:
图2 一个遵循ISP的设计图
对应的设计代码如下:
interface I1 { public void method1(); } interface I2 { public void method2(); public void method3(); } interface I3 { public void method4(); public void method5(); } class A{ public void depend1(I1 i){ i.method1(); } public void depend2(I2 i){ i.method2(); } public void depend3(I2 i){ i.method3(); } } class B implements I1, I2{ public void method1() { System.out.println("类B实现接口I1中的方法1"); } public void method2() { System.out.println("类B实现接口I2中的方法2"); } public void method3() { System.out.println("类B实现接口I2中的方法3"); } } class C{ public void depend1(I1 i){ i.method1(); } public void depend2(I3 i){ i.method4(); } public void depend3(I3 i){ i.method5(); } } class D implements I1, I3{ public void method1() { System.out.println("类D实现接口I1中的方法1"); } public void method4() { System.out.println("类D实现接口I3中的方法4"); } public void method5() { System.out.println("类D实现接口I3中的方法5"); } }
7.2 一个在需求变化中,才发现接口粒度过大的例子:
星控找美女的过程:
图3 初步的星探找美女图类,美女必须 长得好看、身材好、有气质
但是随着人们审美水品的不断提升,人们对气质美女也产生了很大的认同感,即不太要求长相与身材,这时,新的类图如下:(实为7.1节的演化版)
图4 新类图,如果一开始能做到此,便能防患于未然
转自:/article/2368987.html
相关文章推荐
- 设计模式之六大原则——接口隔离原则(ISP)
- 设计模式六大原则(4):接口隔离原则ISP(Interface Segregation Principle)
- 设计模式六大原则——接口隔离原则(ISP,Interface Segregation Principle)
- 设计模式之六大原则——接口隔离原则(ISP)
- 设计模式六大原则例子(一)-- 接口隔离原则(ISP)例子
- 设计模式六大原则(一)-- 接口隔离原则(ISP)
- [置顶] 设计模式之六大原则——接口隔离原则(ISP)
- 设计模式之六大原则——接口隔离原则(ISP)
- 设计模式六大原则之--接口隔离原则(ISP)
- 设计模式值六大原则——接口隔离原则 (ISP)
- 设计模式之六大原则——接口隔离原则(ISP)
- 设计模式之六大原则——接口隔离原则(ISP)
- IOS设计模式的六大设计原则之接口隔离原则(ISP,Interface Segregation Principle)
- 设计模式六大原则(4):接口隔离原则(Interface Segregation Principle)
- 设计模式六大原则(4):接口隔离原则
- 设计模式六大原则(4):接口隔离原则
- 设计模式六大原则(4):接口隔离原则
- 设计模式六大原则(4):接口隔离原则
- IOS设计模式的六大设计原则之接口隔离原则(ISP,Interface Segregation Principle)
- 设计模式六大原则(4)——接口隔离原则