閱讀35 返回首頁    go 阿裏雲 go 技術社區[雲棲]


JUnit源碼分析(四)——從Decorator模式說起

其實我這係列小文,名為源碼分析,其實是自己讀《設計模式》的讀書筆記。Decorator模式在java的IO庫中得到應用,java的IO庫看起來複雜,其實理解了Decorator模式再回頭看可以很好理解並使用。
    Decorator模式,也就是裝飾器模式,是對象結構型模式之一。

1.意圖:動態地給一個對象添加一些額外的職責。給對象添加功能,我們首先想到的是繼承,但是如果每增一個功能都需要繼承,類的繼承體係將無可避免地變的龐大和難以理解。麵向對象設計的原則:優先使用組合,而非繼承,繼承的層次深度最好不過三。

2.適用場景:
1)在不影響其他對象的情況下,以動態、透明的方式給單個對象添加額外的責任
2)處理可以撤銷的職責
3)為了避免類的數目爆炸,或者不能采用生成子類的方法進行擴展時

3.UML圖和協作:

ShowImg.jpg

Component——定義一個對象接口,可以給這些對象動態地添加職責

ConcreteComponent——定義一個對象,可以給這個對象添加職責

Decorator——維持一個指向Component的引用,並定義一個與Component一致的接口,作為裝飾類的父類

ConcreteDecorator——具體裝飾類

4.效果:
1)與靜態繼承相比,Decorator可以動態添加職責,更為靈活
2)避免產生複雜的類,通過動態添加職責,而不是一次性提供一個萬能的接口
3)缺點是將產生比較多的小對象,對學習上有難度,顯然,java.io就是這個問題

我們以一個例子來實現Decorator模式,假設這樣一個場景:在某個應用中需要打印票據,我們寫了一個PrintTicket接口,然後提供一個實現類(DefaultPrintTicket)實現打印的功能:

package com.rubyeye.design_pattern.decorator;
//抽象component接口
public interface PrintTicket {
    
public void print();
}


//默認實現類,打印票據
package com.rubyeye.design_pattern.decorator;

public class DefaultPrintTicket implements PrintTicket {

    
public void print() {
        System.out.println(
"ticket body");
    }

}

OK,我們的功能已經實現,我們還體現了針對接口編程的原則,替換一個新的打印方式很靈活,但是客戶開始提需求了——人生無法避免的三件事:交稅、死亡和需求變更。客戶要求打印頁眉,你首先想到的是繼承:
package com.rubyeye.design_pattern.decorator;

public class AnotherPrintTicket implements PrintTicket {

    
public void print() {
        System.out.println(
"ticket header");
        System.out.println(
"ticket body");
    }

}

請注意,我們這裏隻是簡單的示例,在實際項目中也許意味著添加一大段代碼,並且需要修改打印票據本體的功能。需求接踵而至,客戶要求添加打印頁碼,要求增加打印花紋,要求可以聯打......你的類越來越龐大,直到你看見這個類都想吐的地步!-_-。讓我們看看另一個方案,使用Decorator模式來動態地給打印增加一些功能,首先是實現一個Decorator,它需要保持一個到PrintTicket接口的引用:

package com.rubyeye.design_pattern.decorator;

public class PrintTicketDecorator implements PrintTicket {

    
protected PrintTicket printTicket;

    
public PrintTicketDecorator(PrintTicket printTicket) {
        
this.printTicket = printTicket;
    }

    
//默認調用PrintTicket的print
    public void print() {
        printTicket.print();
    }

}

然後,我們實現兩個具體的裝飾類——打印頁眉和頁腳:
package com.rubyeye.design_pattern.decorator;

public class HeaderPrintTicket extends PrintTicketDecorator {

    
public HeaderPrintTicket(PrintTicket printTicket){
        
super(printTicket);
    }
    
    
public void print() {
        System.out.println(
"ticket header");
        
super.print();
    }
}

package com.rubyeye.design_pattern.decorator;

public class FooterPrintTicket extends PrintTicketDecorator {
    
public FooterPrintTicket(PrintTicket printTicket) {
        
super(printTicket);
    }

    
public void print() {
        
super.print();
        System.out.println(
"ticket footer");
    }
}

    使用起來也很容易:
   
package com.rubyeye.design_pattern.decorator;

public class DecoratorTest {

    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        PrintTicket print
=new HeaderPrintTicket(new FooterPrintTicket(new DefaultPrintTicket()));
        print.print();
    }

}

輸出:
ticket header
ticket body
ticket footer

    了解了Decorator模式,我們聯係了下JUnit裏麵的應用。作為一個測試框架,應該方便地支持二次開發,也許用戶開發自己的TestCase,添加自定義的功能,比如執行重複測試、多線程測試等等。動態添加職責,而又不想使用靜態繼承,這正是Decorator使用的地方。在junit.extensions包中有一個TestDecorator,正是所有裝飾類的父類,也是作為二次開發的基礎,它實現了Test接口,而Test接口就是我們定義的抽象接口:
public class TestDecorator implements  Test {
    
//保有一個指向Test的引用
        protected Test fTest;
       
    
public TestDecorator(Test test) {
        fTest
= test;
    }
        dot.gif
        
public void basicRun(TestResult result) {
          fTest.run(result);
        }
        
public void run(TestResult result) {
           basicRun(result);
        }
        dot.gif
}

Junit已經提供了兩個裝飾類:junit.extensions.ActiveTest用於處理多線程,junit.extensions.RepeatedTest用於執行重複測試,看看RepeatedTest是怎麼實現的:
public class RepeatedTest extends  TestDecorator {
        
//重複次數
    private int fTimesRepeat;

    
public RepeatedTest(Test test, int repeat) {
        
super(test);
        fTimesRepeat
= repeat;
    }
        
public void run(TestResult result) {
        
//重複執行
                for (int i= 0; i < fTimesRepeat; i++) {
            
if (result.shouldStop())
                
break;
            
super.run(result);
        }
    }
        dot.gifdot.gif
}

    RepeatedTest繼承TestDecorator ,覆寫run(TestReult result)方法,重複執行,super.run(result)將調用傳入的TestCase的run(TestResult result)方法,這已經在TestDecorator默認實現。看看使用方式,使用裝飾模式的好處不言而喻。

TestSuite suite = new TestSuite();
suite.addTest(
new TestSetup(new RepeatedTest(new Testmath("testAdd"),12)));
文章轉自莊周夢蝶  ,原文發布時間5.17

最後更新:2017-05-17 13:35:02

  上一篇:go  org.springframework.core.styler包解讀
  下一篇:go  JUnit源碼分析 (三)——Template Method模式