敏捷软件开发:原则、模式与实践——第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)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口.类间的 ...
随机推荐
- 《微信小程序七日谈》- 第一天:人生若只如初见
<微信小程序七日谈>系列文章: 第一天:人生若只如初见: 第二天:你可能要抛弃原来的响应式开发思维: 第三天:玩转Page组件的生命周期: 第四天:页面路径最多五层?导航可以这么玩 微信小 ...
- Linux用户组管理
用户组信息存放:/etc/group 当组内只有一个用户,并且用户名和用户组名一致时默认不显示:组号1-499是系统预留的用户组,>=500的用户组是管理员手动创建的,组密码占位符用x表示 ...
- 好用的ASP.NET 分页类 简单好用 支持 AJAX 自定义文字
在做网站没用 JS UI控件时 很实用 用法: var ps=new PageString(); /*可选参数*/ ps.SetIsEnglish = true;// 是否是英文 (默认:false) ...
- hive的内部表与外部表创建
最近才接触Hive.学到了一些东西,就先记下来,免得以后忘了. 1.创建表的语句:Create [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_na ...
- <!DOCTYPE> 的理解
[[ from <!DOCTYPE> 标签的深入理解 ]] 前言: 今天被问道“有没有仔细了解过<!DOCTYPE>标签?”,愣了一下,因为一开始在W3cschool上看到过建 ...
- NetworkComms.Net github下载地址
https://github.com/MarcFletcher/NetworkComms.Net
- 【Unity】13.2 通过Lighting Window设置相关参数
分类:Unity.C#.VS2015 创建日期:2016-05-19 一.简介 Unity 5.3.4的Lighting Window有3个选项卡:Object.Scene.Lightmaps. 二. ...
- windbg学习进阶之——dump分析常用命令收集
#重要说明 (1) windbg命令分为标准命令,元命令和扩展命令. 标准命令提供最基本的调试功能,不区分大小写.如:bp g dt dv k等 元命令提供标准命令没有提供的功能,也内建在调试 ...
- 不可或缺 Windows Native (1) - C 语言: hello c
[源码下载] 不可或缺 Windows Native (1) - C 语言: hello c 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 在 Windows Sto ...
- DevExpress GridControl功能总结
写在前面,Dev控件已经很久了,功能也很强大,截止到现在我编写文档出来的Dev的版本已经到了14.1了,看了Demo真的很强大,效果也很好,结合自己这一个月开发,分享一下自己研究过后的经验,不让大家走 ...