第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 implementationUIGlobals.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

转载于:https://www.cnblogs.com/jesselzj/p/4765247.html

敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则相关推荐

  1. 【软件项目管理】敏捷软件开发 —— Scrum模式

    什么是敏捷软件开发 前提 : 传统软件开发模式 -- 瀑布模型 在谈论这个问题之前,我们先来回顾一下传统软件开发模式 -- 瀑布模型 在瀑布模型中,软件开发过程被分为严格的 需求分析 -- 系统设计 ...

  2. 《敏捷软件开发(原则模式与实践)》读书笔记

    <敏捷软件开发>读书分享 由于书是由英文书籍翻译,读起来会难免拗口,本次分享是由<敏捷软件开发>结合网上相关资料总结而成. 传统的瀑布式开发 瀑布模型式是最典型的预见性的方法, ...

  3. 敏捷软件开发:原则、模式与实践(全)笔记

    敏捷软件开发宣言 个体和交互 胜过 过程和工具 可以工作的软件 胜过 面面俱到 客户合作 胜过 合同谈判 响应变化 胜过 遵循计划 虽然右项也具有价值, 但我们认为左项具有更大的价值. 敏捷宣言遵循的 ...

  4. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  5. 软件设计原则之接口隔离原则、合成复用原则、迪米特原则

    系列文章目录 软件设计原则之单一职责原则.开闭原则 软件设计原则之里氏替换原则.依赖倒置原则 软件设计原则之接口隔离原则.合成复用原则.迪米特原则 文章目录 系列文章目录 一.接口隔离原则 什么是接口 ...

  6. 软件设计模式六大原则之四接口隔离原则(PHP语言实现)

    转载,原文链接https://www.cnblogs.com/zhanghengscnc/p/8299459.html 定义:客户端不应该依赖它不需要的接口:一个类对另一个类的依赖应该建立在最小的接口 ...

  7. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  8. java 接口隔离_《Java设计模式及实践》—1.5.4 接口隔离原则

    1.5.4 接口隔离原则 下面这句话从链接https://www.oodesign.com/interface-segregation-principle.html得来: "客户端不应该依赖 ...

  9. 五大软件设计原则学习笔记4——接口隔离原则

    五大软件设计原则SOLID: 单一职责原则(Single responsibility principle,SRP) 开放封闭原则(Open–closed principle,OCP) Liskov ...

最新文章

  1. linux文件移出目录命令_Linux 文件与目录管理详解
  2. ajax发不出去请求_Ajax请求发送成功但不进success的解决方法
  3. MFC中小笔记(三)
  4. 实战Solaris 10
  5. 注册中心—组件—ZooKeeper
  6. 为什么要学jquery
  7. Fix an “Unapproved Caller” SecurityAgent Message in Mac OS X
  8. Python基础教程学习目录 - Python入门教程
  9. C语言链表存储数据并排序,c语言求助:怎么根据链表中的某个数据对链表进行排序?...
  10. 使用Python内置集合对象和内置函数filter()过滤无效书评
  11. 数据增长率怎么算_2019 年“泰迪杯”数据分析职业技能大赛A题 超市销售数据分析...
  12. 2021年高考传媒校考成绩查询,中国传媒大学2021年艺术校考合格线及成绩什么时候公布(附查询入口)...
  13. unix网络编程——网络基础
  14. 身份证前6位匹配户籍地址对应的数据地区sql脚本(里面包含json版本的)
  15. MYSQL不能远程连接
  16. IDEA安装及破解永久版教程————鹏鹏
  17. 颜色迁移之四——模糊聚类(FCM)算法
  18. iOS 音乐 锁屏显示 控制
  19. python中的框架是什么_python开发框架介绍
  20. jQuery(一)jQuery的认识和使用

热门文章

  1. jni中native通过adb输出
  2. [ECMAScript] 谈谈你对es6的module体系的理解
  3. Taro+react开发(52) 移动端弹出框使用
  4. 前端学习(3130):react-hello-react之回调形式的createRef
  5. 前端学习(3058):vue+element今日头条管理-回顾
  6. [html] canvas生成图片有没有跨域问题?如果有如何解决?
  7. 工作399-openType=“getUserInfo“ lang=“zh_CN“ bindgetuserinfo=“getUserInfo“
  8. [vue] 说说组件的命名规范
  9. 工作219:富文本查看 编辑状态显示
  10. 前端学习(1410):多人管理30数据分页