面向对象设计 接口隔离(ISP)

ISP

主目录:一个面向对象设计(OOD)的学习思路设计c++

引入:bash

老手机: 大家这些年轻手机光溜溜的,全身上下只有两个插孔几个按钮,为啥这么受欢迎? 新手机:老前辈,您虽然占了一半都是按钮,能够快速的点到,可是多数状况下都没用呀!我虽然只有几个按钮,但都是常常用到滴。我也能达到和你同样的效果,并且更简洁。 老手机:恩,人们只有打字的时候才用到那些按钮。 新手机:因此在日常时候,我这几个按钮就能够知足大部分须要了。 老手机:真是一代比一代强咯!ide

ISP.png

1.何为ISP?

  • 全称:接口隔离原则(Interface Segregation Principle)
  • 定义:客户程序不该该被迫依赖于它们不使用的方法

2.如何理解ISP?

  • 好比图2-1中的鸵鸟类不该该被迫依赖于不使用的飞翔方法 学习

    2-1.违反了ISP

  • 如今将2-1的例子中的接口鸟进行拆分,能飞的鸟类麻雀实现接口飞鸟,不能飞的鸟类鸵鸟实现接口鸟,以下图2-2所示。ui

2-2.知足ISP.png

  • 可能到这里你们有个疑惑:接口变多了!对!就是接口变多了。不是上面还举例了手机的例子吗?阐明了减小接口的好处。
  • 其实咱们减小并非接口,而是接口中的抽象方法。
  • 经过分离来知足客户端的需求,使客户端程序中只存在须要的方法。
  • 客户端的不一样需求才是致使接口改变的缘由。

3.遵循ISP有什么好处?

  • 不遵循ISP而致使的一些问题,在图2-1中,鸵鸟是不须要飞的,但保留了飞的方法。this

  • 如今接口中的飞()方法须要进行改动,假如改为:boolean fly()---能够理解为调用一次向上飞,再调用一次向下飞,依次循环。spa

  • 如今不只会飞的鸟须要改动,连鸵鸟这些不会飞的鸟都要莫名奇妙的跟着去改动。设计

  • 显然这致使了程序之间的耦合加强,影响到了不该该影响的客户程序code

  • 如今正过来看遵循ISP接口,如图2-2所示的例子,分离了方法,使得更改时并不会影响到不相干的客户程序(鸵鸟类)对象

  • 须要尽量避免这种耦合,所以咱们但愿分离接口。

  • 能够看出,分离接口有利于咱们对需求变动时的快速高效的执行行动。

  • 而且使之解构层次更加的分明

4.按部就班的例子(来自敏捷软件开发[^foot1])

以ATM用户界面为例

  1. ATM的用户界面有不一样的交易模式,现将从ATM的基类Transaction(交易类)中派生子类:
  • DepositTransaction存款
  • WithdrawalTransaction取款
  • TransferTransaction转帐
  1. 每个子类交易都有一个界面,所以要依赖于UI,调用的不一样方法,如:DepositTransaction会调用UI类中的RequestDepositAmount()方法,当前ATM结果以下图4-2-1所示。
    4-2-1.ATM操做解构
  • 这样作是ISP告诉咱们应当避免的情形
  • 每一个操做使用的UI方法,其余的操做都不会使用
  • 当每次Transaction子类的改动都会迫使对UI进行改动,从而影响到了其余全部Transaction子类及其余全部依赖于UI接口的类。
  • 当要增长一个支付煤气费的交易时,为了处理该操做想要显示的特定消息,就须要在UI中加入新的方法。糟糕的是,因为Transaction的子类所有依赖于UI接口,因此它们都须要从新编译。
  1. 所以如今有一个办法,将UI接口分解成像DepositUIWithdrawalUI以及TransferUI这样的单独接口,能够避免这种不合适的耦合,最终的UI接口能够去多重继承这些单独的接口。图5-3-1和以后的代码展现了这个模型。
    5-3-1.分离的ATM接口

定义交易接口

/** 存款UI接口*/
   interface DepositUI {
       void RequestDepositAmount();
   }

   /** 取款UI接口*/
   interface WithdrawalUI {
       void RequestWithdrawalAmount();
   }

   /** 转帐UI接口*/
   interface TransferUI {
       void RequestTransferAmount();
   }

   /** UI接口继承全部的交易接口*/
   interface UI extends DepositUI, WithdrawalUI, TransferUI{

   }
复制代码

交易抽象类

/** 交易类*/
   abstract class Transaction {
       public abstract void Execute();
   }
复制代码

交易派生类

/** 存款交易类*/
   class DepositTransaction extends Transaction {
       private DepositUI mDepositUI;
       public DepositTransaction(DepositUI mDepositUI) {
           this.mDepositUI = mDepositUI;
       }

       @Override
       public void Execute() {
           //...
           mDepositUI.RequestDepositAmount();
           //...
       }
   }

   /** 取款交易类*/
   class WithdrawalTransaction extends Transaction {
       private WithdrawalUI mWithdrawalUI;
       public WithdrawalTransaction(WithdrawalUI mWithdrawalUI) {
           this.mWithdrawalUI = mWithdrawalUI;
       }
       @Override
       public void Execute() {
           //...
           mWithdrawalUI.RequestWithdrawalAmount();
           //...
       }
   }

   /** 转帐交易类*/
   class TransferTransaction extends Transaction {
       private TransferUI mTransferUI;
       public TransferTransaction(TransferUI mTransferUI) {
           this.mTransferUI = mTransferUI;
       }
       @Override
       public void Execute() {
           //...
           mTransferUI.RequestTransferAmount();
           //...
       }
   }
复制代码

建立交易对象:因为每一个操做都必须以特定的方式知晓UI版本,如TransferTransaction必须知道TransferUI。在程序中,使每一个操做的构造时给它传入指向特定于它的UI的引用,从而解决这个问题。以下进行初始化

UI GUI;
   void fun() {
       DepositTransaction mDepositTransaction = new DepositTransaction(GUI);
   }
复制代码

虽然这样很方便,但一样要求每一个操做都有一个指向对应UI的引用成员。另一种解决这个问题的方法是建立一组全局常量。全局变量并不老是意味着拙劣的设计,在这种状况下,它们有着明显的易于访问的有点。

/** UI全局变量*/
class UIGlobals {
   public static DepositUI mDepositUI;
   public static WithdrawalUI mWithdrawalUI;
   public static TransferUI mTransferUI;
   public UIGlobals(UI lui) {
       UIGlobals.mDepositUI = lui;
       UIGlobals.mWithdrawalUI = lui;
       UIGlobals.mTransferUI = lui;
   }
}
复制代码
/** 转帐交易类*/
class TransferTransaction extends Transaction {
   @Override
   public void Execute() {
       //...
       UIGlobals.mTransferUI.RequestTransferAmount();
       //...
   }
}
复制代码
/**
* UI的实现类
*/
class UIEntity implements UI {

   @Override
   public void RequestDepositAmount() {
       //...
   }

   @Override
   public void RequestTransferAmount() {
       //...
   }

   @Override
   public void RequestWithdrawalAmount() {
       //...
   }
}
复制代码
/**
* 使用
*/
class A {
   //初始化UI静态类
   UIGlobals mUIGlobals = new UIGlobals(new UIEntity());

   //调用姿式
   void fun() {
       Transaction mTransaction = new TransferTransaction();
       mTransaction.Execute();
   }
}
复制代码

因为敏捷软件开发举的例子是c++的,知识有限,表示不少看不懂,可能有些地方误差较大,想了解更多建议亲自去看看( ¯▽¯;)

5.总结

  • 胖类(fat class):就是上边讲解的不知足ISP的类型

  • 能够看出胖类加强了类之间的耦合,使得对该胖类进行改动会影响到全部其余类。

  • 经过将胖类接口分解成多个特定类(客户端程序)的接口,使得强耦合得以解决

  • 而后该胖类继承全部特定类的接口,并实现它们。就解除了这个特定类和它没有调用方法间的依赖关系,并使得这些特定类之间互不依赖。

6.参考文献[^foot2]

[^foot1]: 敏捷软件开发 第12章 接口隔离原则(ISP) [^foot2]: 如何向妻子解释OOD

相关文章
相关标签/搜索