zoukankan      html  css  js  c++  java
  • 敏捷开发原则与实践(七)之接口隔离原则

    接口隔离原则(The Interface-Segregation Principle)强调类的功能要单一,类的功能臃肿增加不必要的耦合,增加代码的脆弱性,还会增加编译依赖。该原则建议将方法分组,达到隔离接口的目的,具体的方法有委托和多重继承。

    在Timed Door的例子中,开始的实现是Timed Door继承Door,Door实现Timer Client这个接口,这样Timed Door就可以通过Timer Client这个接口去使用Timer的功能。但这样做会导致Door依赖于Timer Client,而且并不是所有的Door都需要定时功能。所有的继承于Door的子类都需要提供退化的Timeout方法,违反了LSP

    注意,这里的TimerClient是Timer实现的需求,与Door无关。所有使用Timer的类对相当于Timer的client,需要实现TimerClient这个接口,用于Timer time out时调用。

    clip_image002

    Timer Client at Top of Hierarchy

    Door和Timer Client中的接口分别由不同的client使用,既然使用者相互独立,接口也应该独立,因为使用者也会对接口产生影响。我们一般都会考虑接口对使用者的影响,但使用者也会对接口提出更多的要求。比如文中由于door的需求,需要Timer提供带timeOutId的注册接口。但这个接口的变化会导致Door的所有子类代码都需要修改,这个设计的确很糟糕。

    接口隔离原则:使用者不应该被逼去依赖它们不使用的方法。

    Clients should not be forced to depend on methods that they do not use.

    通过委托或多重继承可以避免依赖不使用的方法。一般使用多重继承,因为委托会产生多余的性能和空间的耗用。这里的委托就是说将对TimerClient的实现委托给TimerClientAdapter去做。

    TimedDoor例子中通过将定时接口和Door接口隔离,避免了其它Door子类依赖于定时接口。

     clip_image004

    Multiply inherited Timed Door

     

    clip_image006

    Door Timer Adapter

     1 public interface TimerClient {
     2     public void timeOut();
     3 }
     4 
     5 public class Timer2 extends TimerTask{
     6 
     7     private TimerClient mTc;
     8     private Timer mTimer;
     9     
    10     public void registry(int timeout, TimerClient tc) {
    11         mTc = tc;
    12         mTimer = new Timer();
    13         mTimer.schedule(this, timeout*1000);
    14     }
    15 
    16     @Override
    17     public void run() {
    18         // TODO Auto-generated method stub
    19         mTc.timeOut();
    20     }
    21 }
    Timer以及接口
     1 public class TimedDoor2 extends Door implements TimerClient {
     2 
     3     public TimedDoor2(int second) {
     4         super();
     5         Timer2 t = new Timer2();
     6         t.registry(second, this);
     7     }
     8 
     9     public void doorTimeOut () {
    10         if (!mIsLock) {
    11             lock();
    12         }
    13     }
    14     
    15     @Override
    16     public void timeOut() {
    17         // TODO Auto-generated method stub
    18         doorTimeOut();
    19     }
    20 
    21 }
    TimerDoor多重继承实现
     1 public class DoorTimerAdapter implements TimerClient {
     2     private TimedDoor mTimedDoor;
     3     
     4     public DoorTimerAdapter(TimedDoor td) {
     5         mTimedDoor = td;
     6     }
     7 
     8     @Override
     9     public void timeOut() {
    10         // TODO Auto-generated method stub
    11         mTimedDoor.doorTimeOut();
    12     }
    13 
    14 }
    15 
    16 public class TimedDoor extends Door {
    17     
    18     public TimedDoor() {
    19         super();
    20         Timer2 t = new Timer2();
    21         t.registry(5, new DoorTimerAdapter(this));
    22     }
    23 
    24     public void doorTimeOut () {
    25         if (!mIsLock) {
    26             lock();
    27         }
    28     }
    29 
    30 }
    31 
    32 public class Timer2 extends TimerTask{
    33 
    34     private TimerClient mTc;
    35     private Timer mTimer;
    36     
    37     public void registry(int timeout, TimerClient tc) {
    38         mTc = tc;
    39         mTimer = new Timer();
    40         mTimer.schedule(this, timeout*1000);
    41     }
    42 
    43     @Override
    44     public void run() {
    45         // TODO Auto-generated method stub
    46         mTc.timeOut();
    47     }
    48 }
    49 
    50 public interface TimerClient {
    51     public void timeOut();
    52 }
    TimerDoor委托实现

    在这里说一下对接口的理解。之前对接口的使用一直局限在多态的使用上,即定义一个接口,加上若干实现,所有定义对象的地方可以使用接口定义,运行时创建不同的对象。因为这个局限,导致自己一直无法理解抽象类和接口到底有什么区别,觉得抽象类和接口是可以互换的。

    仔细想想,接口定义的是行为。一个类实现一个接口,说明它具备这些行为能力,比如TimedDoor实现Timer Client接口后,说明它具备定时功能。一个类可以具备很多行为能力,还有多个类会具备相同的行为能力,比如你也可以开发一个实现Timer Client接口的TimedCar类,它也具备定时功能。在这里,就需要使用接口来将各种各样的行为进行分类,类需要什么功能,就实现什么样的接口。

    抽象类中的定义的方法也可以称为行为,它与接口定义的行为有什么区别呢?抽象类是对类的抽象,接口是对行为的抽象。每个类都会有自己的一些基本行为,它是在我们对现实世界进行抽象时得到的,比如Door肯定具备open、close行为。同时各个类之间又会具备一些不同的行为,我们不能保证,不可能所有类具备同样的行为,这时就需要接口来作为补充。刚开始往往认为代码中有抽象类和接口才是面向对象编程,这是错误的。滥用抽象类和接口会增加程序的复杂度,我们应该只在需要时才进行重构使用抽象类和接口

    TimedDoor例子中是该类调用的接口需要隔离,即Door需要实现的功能太多了,非TimedDoor没必要实现TimerClient;ATM机的例子中则是UI中被Trasaction调用的接口需要分类隔离,即UI提供的功能太多了。

    ATM需要在多种终端上实现不同类型的UI,比如盲文、触摸屏、按键等,这就要求UI的实现需要非常flexible。相反,ATM后台事务逻辑很固定,几乎不会变化。因此就把UI对象传递给事务,让事务去请求UI做事情,比如要求用户输入等。以前,我们一般都会认为UI去调用事务逻辑,这里刚好相反,很巧妙!

    但之前将所有的UI行为(存钱UI、取钱UI等)放在一个接口中,任何一个Transaction发生变化时,如果影响了UI接口,那么就会将这种变化传递给其它Transaction,因为它们都依赖于UI。最典型的就是当增加一个Transaction时,UI中必须增加一个接口,这时就会导致所有Transaction重新编译。

    clip_image008

    ATM Transaction Hierarchy

    image

    Segregated ATM UI Interface

    最后,当接口发生变化时,为了防止由此导致的大规模重新编译和重新部署,可取的方法是新增接口,而不是修改原有的接口。

  • 相关阅读:
    DS博客作业02--线性表
    DS博客作业01--日期抽象数据类型设计与实现
    C语言博客作业06--结构体&文件
    C语言博客作业05--指针
    C语言博客作业04--数组
    DS博客作业8——课程总结
    DS博客作业07——查找
    DS博客作业06--图
    DS博客作业——树
    DS博客作业03--栈和队列
  • 原文地址:https://www.cnblogs.com/ustbdavid/p/3479609.html
Copyright © 2011-2022 走看看