敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则
第12章 ISP:接口隔离原则
不应该强迫客户程序依赖并未使用的方法。
这个原则用来处理“胖”接口所存在的缺点。如果类的接口不是内敛的,就表示该类具有“胖”接口。换句话说,类的“胖”接口可以分解成多组方法。每一组方法都服务于一组不同的客户程序。这样,一些客户程序可以使用一组成员函数,而其他客户程序可以使用其他组的成员函数。
ISP承认一些对象确实需要非内敛的接口,但是ISP建议客户不应该看到它们作为单一的类存在。相反,客户程序看到的应该是多个具有内敛接口的抽象基类。
12.1 接口污染
如果子类的接口中有子类不需要的方法,就说这个接口被污染了。在接口中加入方法,只为了能给它的其中一个子类带来好处。如果持续这样的话,那么每次子类需要一个新方法时,这个方法就会加到基类中去。这会进一步污染其基类的接口,使它变“胖”。
每次基类中加入一个方法时,派生类中就必须实现这个方法(或者定义一个默认的实现)。事实上,一种特定的相关实践,可以使派生类无需实现这些方法,该实践的方法就是把这些接口合并成一个基类,并在这个基类中提供接口中方法的退化实现。但是,这种实践违反了LSP,会带来维护和重用方面的问题。
12.2 分离客户就是分离接口
不应该强迫客户程序依赖并未使用的方法。如果强迫客户依赖于它们不使用的方法,那么这些客户程序就面临着由于这些未使用方法的改变带来的变更。这无意中导致了所有客户程序之间的耦合。换种说法,如果一个客户程序依赖于一个含有它不使用的方法的类,但是其它客户程序却要使用该方法,那么当其他客户程序要求这个类改变时,就会影响到这个客户程序。我们希望尽可能的避免这种耦合,因此我们希望分类接口。
12.3 类接口与对象接口
一个对象的客户程序不必通过该对象的接口来访问它,也可以通过委托或者通过该对象的基类来访问它。
12.3.1 使用委托分离接口
这里的“委托”不是我们说的“委托类型”的委托,原文翻译的容易产生歧义。其实就是用适配器来解耦客户程序和接口。
不过,这个解决方案还有些不太优雅。每次想要注册一个请求,都要去创建一个新的对象。
12.3.2 使用多重继承分离接口
通常我会优先选择这个解决方案。只有当Adapter对象所做的转换是必须的,或者不同的时候需要不同的转换时,才使用适配器的方案。
12.4 ATM用户界面的例子
ATM(自动取款机)需要一个非常灵活的界面。它的输出信息需要转换成不同的语言。输出信息可能显示在屏幕上,或者盲文书写板,或者通过语音合成器说出来。通过创建一个抽象类就可以实现这种需求:
同样可以把每个ATM执行的不同事务(存储、取款、转账)封装为类Transaction的派生类:
注意,这正好是LSP告我我们应该避免的情形。每个事务所使用的UI的方法,其他操作类都不会使用,这样对于任何一个Transaction的派生类的改动都会迫使对UI的相应改动,从而也影响到了其他所有Transaction的派生类及其他所有依赖于UI接口的类。这样的设计就有了僵化性和脆弱性。
这时需要分类ATM的UI接口:
这样,每次创建一个Transaction类的新派生类,抽象接口UI就需要增加一个相应的基类,并且因此UI接口以及所有它的派生类都必须改变。不过,这些类并没有被广泛使用。事实上,它们可能仅被main或者那些启动系统并创建具体UI实例之类的过程使用。因此,增加新的UI基类所带来的影响被减至最小。
查看如下代码:
public interface Transaction
{
void Execute();
}
public interface DepositUI
{
void RequestDepositAmount();
}
public class DepositTransaction : Transaction
{
privateDepositUI depositUI;
public DepositTransaction(DepositUI ui)
{
depositUI = ui;
}
public virtual void Execute()
{
/*code*/
depositUI.RequestDepositAmount();
/*code*/
}
}
public interface WithdrawalUI
{
void RequestWithdrawalAmount();
}
public class WithdrawalTransaction : Transaction
{
private WithdrawalUI withdrawalUI;
public WithdrawalTransaction(WithdrawalUI ui)
{
withdrawalUI = ui;
}
public virtual void Execute()
{
/*code*/
withdrawalUI.RequestWithdrawalAmount();
/*code*/
}
}
public interface TransferUI
{
void RequestTransferAmount();
}
public class TransferTransaction : Transaction
{
private TransferUI transferUI;
public TransferTransaction(TransferUI ui)
{
transferUI = ui;
}
public virtual void Execute()
{
/*code*/
transferUI.RequestTransferAmount();
/*code*/
}
}
public interface UI : DepositUI, WithdrawalUI, TransferUI
{
}
每个事务都必须以某种方式知晓它的特定UI版本。使每个事务在构造时给它传入特定于它的UI的引用就解决了这个问题。这使我们可以使用如下代码:
UI Gui; // global object;
void f()
{
DepositTransaction dt = new DepositTransaction(Gui);
}
虽然这很方便,但是同样要求每个事务都有一个整型对应UI的引用成员。在C#中,一种比较有诱惑力的做法是把所有UI组件放到一个单一的类中,如下:
public class UIGlobals
{
public static WithdrawalUI withdrawal;
public static DepositUI deposit;
public static TransferUI transfer;
static UIGlobals()
{
UI Lui = new AtmUI(); // Some UI implementation
UIGlobals.deposit = Lui;
UIGlobals.withdrawal = Lui;
UIGlobals.transfer = Lui;
}
}
不过,这种做法有一个负面效果。那就是UIGlobal类依赖于DepositUI、WithdrawalUI和TransferUI。UIGlobal类把我们分离的接口重新结合在一起了。
每个原则在应用时都必须小心,不能过度使用它们。如果一个类具有数百个不同接口,其中一些是根据客户程序分离的,另一些是根据版本分离的,那么该类就是难以琢磨的,这种难以琢磨性是非常令人恐惧的。
12.5 结论
胖类会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求改胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定于用户程序的接口,可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户程序的接口,并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,并使客户程序之间互不依赖。
摘自:《敏捷软件开发:原则、模式与实践(C#版)》Robert C.Martin Micah Martin 著
转载请注明出处:
作者:JesseLZJ
出处:http://jesselzj.cnblogs.com
敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则的更多相关文章
- 设计模式原则(4)--Interface Segregation Principle(ISP)--接口隔离原则
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.定义: 使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 2.使用场景: 类A ...
- 八、ISP 接口隔离原则
ISP应用的场景是某些类不符合SRP原则,但使用这些类的客户端应该根据它们的父类来使用(我感觉这句话应该改为:客户端应该根据它们的抽象类\接口来使用它们),而不是直接使用它们. 定义: 客户端不应该依 ...
- 六大设计原则(四)ISP接口隔离原则(上)
ISP的定义 首先明确接口定义 实例接口 我们在Java中,一个类用New关键字来创建一个实例.抛开Java语言我们其实也可以称为接口.假设Person zhangsan = new Person() ...
- ISP接口隔离原则
一.定义 不应该强迫客户程序依赖并未使用的方法 二.接口污染 接口污染,在C#.C++这样的静态类型语言中是很常见的.一个接口会被他不需要的方法污染.在接口中假如一个方法只是为了能给它的一个子类带来好 ...
- 【Scrum】-NO.40.EBook.1.Scrum.1.001-【敏捷软件开发:原则、模式与实践】- Scrum
1.0.0 Summary Tittle:[Scrum]-NO.40.EBook.1.Scrum.1.001-[敏捷软件开发:原则.模式与实践]- Scrum Style:DesignPattern ...
- 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)
Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...
- 敏捷软件开发_实例2<四>
敏捷软件开发_实例2 上一章中对薪水支付案例的用例和类做了详细的阐述,在本篇会介绍薪水支付案例包的划分和数据库,UI的设计. 包的划分 一个错误包的划分 为什么这个包是错误的: 如果对classifi ...
- C#软件设计——小话设计模式原则之:接口隔离原则ISP
前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...
- 第2章 面向对象的设计原则(SOLID):4_接口隔离原则(ISP)
4. 接口隔离原则(Interface Segregation Principle,ISP) 4.1 定义 (1)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口.类间的 ...
随机推荐
- 受限玻尔兹曼机(RBM)学习笔记(一)预备知识
去年 6 月份写的博文<Yusuke Sugomori 的 C 语言 Deep Learning 程序解读>是囫囵吞枣地读完一个关于 DBN 算法的开源代码后的笔记,当时对其中涉及的算法原 ...
- Android 简单计算器源码....
PS:今天算是闲着没事做了一个小型的计算器...顺便熟悉一下Android的布局,组件,以及时间监听的方法...就当是做了一个小小的练习吧... 顺便去对比了一下别人写的代码...有的使用到了 ...
- HT图形组件设计之道(三)
上篇我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动,这类案例后续文章还会继续以便大家掌握更多的矢量应用场景,本篇我们先切换个话题, ...
- Java魔法堂:打包知识点之META-INF/MAINFEST.MF
一.前言 通过执行形如 jar -cvf src.jar src 命令将多个.class文件打包成JAR包时,你会发现JAR包中除了src目录外还多了个MATE-INF/MAINFEST.MF ...
- JS魔法堂:阻止元素被选中
一.前言 在为IE5.5~9polyfill HTML5新特性placeholder时需要阻止元素被选中,因此在网上.书上查阅相关资料,记录在此以便日后查阅. 二.IE10+实现方式──CSS3 .u ...
- 检测局域网中还可用的ip地址
#!/bin/bash ` do { .$i &>/dev/null ];then echo "192.168.1.$i is not used" fi } done
- C#开源资源项目
一.AOP框架 Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种部署方面(as ...
- 重构第16天 封装条件(Encapsulate Conditional)
理解:本文中的“封装条件”是指条件关系比较复杂时,代码的可读性会比较差,所以这时我们应当根据条件表达式是否需要参数将条件表达式提取成可读性更好的属性或者方法,如果条件表达式不需要参数则可以提取成属性, ...
- margin的使用方法与技巧
1.margin还可以用来做平移,作用类似translate哈哈.将元素设成absolute后就可以用margin随便平移他了,既不像relative那样要霸占空间,又不用为父元素设置relative ...
- Linux下建立Nexus私服
Linux下建立Nexus私服 要安装3个东西,然后配置私服: 1.JDK 2.Maven 3.Nexus 然后配置 1.JDK的安装 下载JDK安装包,格式为RPM格式,安装即可 安装程序 #rpm ...