JUnit原始碼分析(四)——從Decorator模式說起
其實我這系列小文,名為原始碼分析,其實是自己讀《設計模式》的讀書筆記。Decorator模式在java的IO庫中得到應用,java的IO庫看起來複雜,其實理解了Decorator模式再回頭看可以很好理解並使用。
Decorator模式,也就是裝飾器模式,是物件結構型模式之一。
1.意圖:動態地給一個物件新增一些額外的職責。給物件新增功能,我們首先想到的是繼承,但是如果每增一個功能都需要繼承,類的繼承體系將無可避免地變的龐大和難以理解。物件導向設計的原則:優先使用組合,而非繼承,繼承的層次深度最好不過三。
2.適用場景:
1)在不影響其他物件的情況下,以動態、透明的方式給單個物件新增額外的責任
2)處理可以撤銷的職責
3)為了避免類的數目爆炸,或者不能採用生成子類的方法進行擴充套件時
3.UML圖和協作:
![](https://i.iter01.com/images/1f9938ae03985cea7f4b056575c1445f1b3dc7b730a3e6e6ac1f39a55d2adbe4.jpg)
Component——定義一個物件介面,可以給這些物件動態地新增職責
ConcreteComponent——定義一個物件,可以給這個物件新增職責
Decorator——維持一個指向Component的引用,並定義一個與Component一致的介面,作為裝飾類的父類
ConcreteDecorator——具體裝飾類
4.效果:
1)與靜態繼承相比,Decorator可以動態新增職責,更為靈活
2)避免產生複雜的類,通過動態新增職責,而不是一次性提供一個萬能的介面
3)缺點是將產生比較多的小物件,對學習上有難度,顯然,java.io就是這個問題
我們以一個例子來實現Decorator模式,假設這樣一個場景:在某個應用中需要列印票據,我們寫了一個PrintTicket介面,然後提供一個實現類(DefaultPrintTicket)實現列印的功能:
OK,我們的功能已經實現,我們還體現了針對介面程式設計的原則,替換一個新的列印方式很靈活,但是客戶開始提需求了——人生無法避免的三件事:交稅、死亡和需求變更。客戶要求列印頁首,你首先想到的是繼承:
請注意,我們這裡只是簡單的示例,在實際專案中也許意味著新增一大段程式碼,並且需要修改列印票據本體的功能。需求接踵而至,客戶要求新增列印頁碼,要求增加列印花紋,要求可以聯打......你的類越來越龐大,直到你看見這個類都想吐的地步!-_-。讓我們看看另一個方案,使用Decorator模式來動態地給列印增加一些功能,首先是實現一個Decorator,它需要保持一個到PrintTicket介面的引用:
然後,我們實現兩個具體的裝飾類——列印頁首和頁尾:
使用起來也很容易:
輸出:
ticket header
ticket body
ticket footer
瞭解了Decorator模式,我們聯絡了下JUnit裡面的應用。作為一個測試框架,應該方便地支援二次開發,也許使用者開發自己的TestCase,新增自定義的功能,比如執行重複測試、多執行緒測試等等。動態新增職責,而又不想使用靜態繼承,這正是Decorator使用的地方。在junit.extensions包中有一個TestDecorator,正是所有裝飾類的父類,也是作為二次開發的基礎,它實現了Test介面,而Test介面就是我們定義的抽象介面:
Junit已經提供了兩個裝飾類:junit.extensions.ActiveTest用於處理多執行緒,junit.extensions.RepeatedTest用於執行重複測試,看看RepeatedTest是怎麼實現的:
RepeatedTest繼承TestDecorator ,覆寫run(TestReult result)方法,重複執行,super.run(result)將呼叫傳入的TestCase的run(TestResult result)方法,這已經在TestDecorator預設實現。看看使用方式,使用裝飾模式的好處不言而喻。
Decorator模式,也就是裝飾器模式,是物件結構型模式之一。
1.意圖:動態地給一個物件新增一些額外的職責。給物件新增功能,我們首先想到的是繼承,但是如果每增一個功能都需要繼承,類的繼承體系將無可避免地變的龐大和難以理解。物件導向設計的原則:優先使用組合,而非繼承,繼承的層次深度最好不過三。
2.適用場景:
1)在不影響其他物件的情況下,以動態、透明的方式給單個物件新增額外的責任
2)處理可以撤銷的職責
3)為了避免類的數目爆炸,或者不能採用生成子類的方法進行擴充套件時
3.UML圖和協作:
![](https://i.iter01.com/images/1f9938ae03985cea7f4b056575c1445f1b3dc7b730a3e6e6ac1f39a55d2adbe4.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");
}
}
//抽象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");
}
}
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();
}
}
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();
}
}
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");
}
}
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();
}
}
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;
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
public void basicRun(TestResult result) {
fTest.run(result);
}
public void run(TestResult result) {
basicRun(result);
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
}
//保有一個指向Test的引用
protected Test fTest;
public TestDecorator(Test test) {
fTest= test;
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
public void basicRun(TestResult result) {
fTest.run(result);
}
public void run(TestResult result) {
basicRun(result);
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.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);
}
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
}
//重複次數
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);
}
}
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.gif)
![](https://i.iter01.com/images/077790aab985d85c4151289b7bd5f13f08bed32be55a175539d312b0ff63de1e.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)));
suite.addTest(new TestSetup(new RepeatedTest(new Testmath("testAdd"),12)));
相關文章
- Vue-Property-Decorator原始碼分析Vue原始碼
- JUnit4.8.2原始碼分析-4 RunNotifier與RunListener原始碼
- Zookeeper原始碼分析(四) —– 叢集模式(replicated)執行原始碼模式
- Zookeeper原始碼分析(四) ----- 叢集模式(replicated)執行原始碼模式
- 從程式碼生成說起,帶你深入理解 mybatis generator 原始碼MyBatis原始碼
- 基於 junit5 實現 junitperf 原始碼分析原始碼
- Koa原始碼閱讀(一)從搭建Web伺服器說起原始碼Web伺服器
- preact原始碼分析(四)React原始碼
- zanphp原始碼解讀 – MVC說起PHP原始碼MVC
- 故障分析 | 從 data_free 異常說起
- OkHttpClient原始碼分析(四)—— CacheInterceptorHTTPclient原始碼
- Netty原始碼分析(四):EventLoopGroupNetty原始碼OOP
- JDK原始碼分析(四)——LinkedHashMapJDK原始碼HashMap
- 從原始碼分析Axios原始碼iOS
- 從 JSON 說起JSON
- 模板方法模式,從網站登入開始說起模式網站
- Retrofit原始碼分析二 代理模式原始碼模式
- Deno原理詳解,讓我們一起從原始碼分析開始原始碼
- TreeMap原始碼分析,看了都說好原始碼
- 從原始碼看flutter(四):Layer篇原始碼Flutter
- Spring是如何整合JUnit的?JUnit原始碼關聯延伸閱讀Spring原始碼
- 設計模式(十四)——模板模式(SpringIOC原始碼分析)設計模式Spring原始碼
- Shading – jdbc 原始碼分析(四) – sql 路由JDBC原始碼SQL路由
- Tomcat原始碼分析 (四)----- Pipeline和ValveTomcat原始碼
- Vue原始碼分析系列四:Virtual DOMVue原始碼
- Flutter Dio原始碼分析(四)--封裝Flutter原始碼封裝
- NEO從原始碼分析看NEOVM原始碼
- 裝飾模式 (Decorator Pattern)模式
- 裝飾器模式(Decorator)模式
- iOS逆向——從RSA說起iOS
- 從SEQUENCE跳號說起
- 從測試說起(二)
- Spring Boot + Junit 5 + Testcontainers原始碼專案Spring BootAI原始碼
- 設計模式--裝飾模式(Decorator Pattern)設計模式
- 設計模式-裝飾模式(Decorator Pattern)設計模式
- Spring原始碼分析(四)SpringMVC初始化原始碼SpringMVC
- redux原始碼分析之四:compose函式Redux原始碼函式
- Android 8.0 原始碼分析 (四) Activity 啟動Android原始碼
- mybaits原始碼分析--日誌模組(四)AI原始碼