程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> IOU設計模式介紹及應用

IOU設計模式介紹及應用

編輯:關於JAVA

原理

IOU 思想是人們在處理日常債務關系時行之有效的一種方法,即:

債務人通過可靠的第三方保管賬戶,向債權人發放 IOU 債務憑證;

債務人通過向第三方保管賬戶提交結果以終止 IOU 債務;

債權人憑此 IOU 債務憑證通過第三方保管賬戶履行債權並進行結果贖回。

債務人和債權人之間的債務關系,通過可靠的第三方保管賬戶,實現了在時間和空間上最大程度的分離和解耦。

IOU 設計模式是 IOU 思想在軟件設計領域的應用,最早由 Allan Vermeulen 於 1996 年首次提出。在軟件設計領域,債務關系發生在方法調用者和方法體之間,債務對象就是方法的返回結果。普通方法的調用模型是方法體同步執行然後返回結果,調用者必須等待結果返回後才能繼續執行。在 IOU 設計模式下,方法體將立即返回一個 IOU 對象,並且承諾 IOU 對象最終一定會被終止,調用者在 IOU 對象被終止後可進行結果的贖回。在此期間,調用者無需等待就能夠繼續進行其它有價值的事務,從而達到了提高程序整體的並發性和異步性的目的。

IOU 設計模式完全不依賴於任何一種異步機制,IOU 對象的提供者可以選擇任意有效的方式來執行服務並最終終止 IOU 對象,比如啟用獨立的線程/進程執行、驅動異步事件產生、通過遠程方法調用或是等待用戶終端輸入等等。這是 IOU 模式具備普遍適用性的一個重要因素。

IOU 模式分析及實現

IOU 模式主要有 Iou(債務憑證)和 Escrow(第三方保管賬戶)兩個對象,模式的實際使用時還會涉及 Caller(調用者)、Callee(被調用者)及 AsyncService(異步服務)等對象。

時序圖

通過時序圖,讀者可以建立對 IOU 模式使用過程的初步印象。

圖 1. IOU 模式時序圖

IOU 接口定義

IOU 對象具備兩種狀態:一是未終止狀態,意味著結果對象尚不可贖回;另一種是已終止狀態,意味著結果對象可贖回。IOU 對象同時需支持四種基本操作:

支持對狀態的查詢操作;

支持等待操作直至其被終止;

支持對結果的贖回操作,若尚未終止則保持等待直至其被終止;

支持添加或刪除回調對象的操作。

IOU 接口定義見清單 1。

清單 1. Iou 接口定義

public interface Iou
{
   // 判斷 IOU 對象是否已終止
   boolean closed();

   // 保持等待直至被終止
   void standBy();

   // 贖回結果,如果 IOU 對象尚未被終止則該方法將保持等待直至終止後再返回結果
   Object redeem();

   // 添加回調對象 cb
   void addCallback(Callback cb);

   // 刪除回調對象 cb
   void removeCallback(Callback cb);
}

Escrow 接口定義

Escrow 是第三方保管賬戶,它實際上扮演了一個橋梁作用。在債務關系建立初期,債務人通過 Escrow 向債權人發行 Iou;當債務關系結束時,債務人通過 Escrow 終止 Iou,並使其進入結果可贖回狀態。如果債權人前期設置了回調對象,回調機制在 Iou 對象被終止時將立即執行債權人所提前設定的特定操作。Escrow 接口定義見清單 2。

清單 2. Escrow 接口定義

public interface Escrow
{
   // 發行 Iou 對象
   Iou issueIou();

   // 終止 Iou 對象,參數是最終結果
   void close(Object o);
}

Callback 接口定義

IOU 模式中的回調機制主要是為了提供一種當 Iou 對象進入結果可贖回狀態時能夠立即執行某些回調動作的能力。每個回調對象都需實現 Callback 接口,並向感興趣的 Iou 對象進行注冊。每個 Iou 對象都會維護一個 Callback 對象列表,每個 Callback 對象在該 Iou 對象被終止時都有機會在結果對象上執行回調操作。Callback 接口定義見清單 3。

清單 3. Callback 接口定義

public interface Callback
{
   // 在結果對象上執行回調任務
   void callback(Object o);
}

IOU 模式的 Java 實現

Iou 接口側重於債權人的操作,而 Escrow 側重於債務人的操作,兩個接口由同一個類來實現可以讓實現變得更加簡潔高效,具體實現見清單 4。

清單 4. RealIouEscrow 實現

public class RealIouEscrow implements Iou, Escrow
{
   // Vector to hold all callbacks
   private Vector callbacks;
   // boolean indicate if IOU has been closed
   private boolean closed;
   // Object that I owe you
   private Object objectIou;

   public RealIouEscrow()
   {
     this.callbacks = new Vector();
     this.closed = false;
   }

   public Iou issueIou()
   {
     // 直接返回對象本身,因為已經實現了 Iou 接口
     return this;
   }

   public synchronized void addCallback(Callback cb)
   {
     if( this.closed )
     {
       // 若已經被終止,則直接回調
       cb.callback(this.objectIou);
     }
     else
     {
       // 否則,將回調對象加入列表
       this.callbacks.add(cb);
     }
   }

   public synchronized void removeCallback(Callback cb)
   {
     // 將回調對象從列表中刪除
     this.callbacks.remove(cb);
   }

   public synchronized boolean closed()
   {
     return this.closed;
   }

   public synchronized Object redeem()
   {
     if( !this.closed )
     {
       // 如果尚未被終止,保持等待
       standBy();
     }
     return this.objectIou;
   }

   public synchronized void standBy()
   {
     if( !this.closed )
     {
       try 
       {
         wait();
       }
       catch (InterruptedException e)
       {
       }
     }
   }

   public synchronized void close(Object o)
   {
     if( !this.closed )
     {
       // 首先設置結果對象
       this.objectIou = o;
       // 然後設置終止標志位
       this.closed = true;
       // 接著喚醒等待線程
       this.notifyAll();
       // 最後驅動回調者執行回調方法
       Iterator it = this.callbacks.iterator();
       while(it.hasNext())
       {
         Callback callback = (Callback)it.next();
         callback.callback(this.objectIou);
       }
     }
   }
}

IOU 模式的使用

從被調方法的角度:首先構造 Escrow 對象,然後啟動異步執行服務並關聯 Escrow 對象,最後返回 Escrow 對象發行的 Iou 對象。被調方法模型如清單 5 所示。

清單 5. 被調方法的實現模型

public Iou method( … )
{
   // 首先創建 escrow 對象
   Escrow escrow = new RealIouEscrow();

   // 啟動異步服務,並關聯 escrow 對象
   ……

   // 返回 escrow 發行的 Iou 欠條
   return escrow.issueIou();
}

從方法調用者的角度:調用者獲得 Iou 對象後,可以繼續進行其他事務,直到需要結果的時候再對 Iou 進行贖回操作以獲得真正結果(假設其真實類型是 Foo 接口,該接口聲明有 bar 方法),則調用者還要把結果轉換到 Foo 類型,然後再調用 bar 方法。調用者模型如清單 6 所示。

清單 6. 調用者的實現模型

// 調用 method 方法,獲得 Iou 對象
   Iou iou = method();

   // 執行其他事務
   ……

   // 通過 Iou 贖回操作獲得真實 result
   Object result = iou.redeem();

   // 將 result 類型轉換到 Foo
   Foo foo = (Foo)result;
   // 然後訪問 bar 方法
   foo.bar();
   …… 

IOU 模式的不足之處

由於 Escrow 發行的都是 Iou 對象,這在無意間要求 IOU 模式下的方法必須統一聲明返回 Iou 接口,從而隱藏了結果的真實類型,用戶必須依靠記憶記住真實類型並強制轉換,然後才能訪問結果。用戶友好性的先天不足,或許是限制 IOU 模式廣泛使用的一大因素。

雙劍合璧:IOU 模式結合 Java 動態代理

魚和熊掌可否兼得

理想的情況下,用戶會希望 IOU 模式下方法的返回類型依然是真實類型。似乎是“魚和熊掌不可兼得”式的矛盾,因為根據傳統的觀點,一個方法是無法返回兩種類型的(尤其當兩種類型又無必然的聯系時)。但是,Java 動態代理機制給我們帶來了希望(本文假設讀者對 Java 動態代理機制已經有所了解,不了解的讀者請查閱相關資料)。通過 Java 動態代理機制,我們能夠動態地為一組目標接口(允許是任意不相關的接口)創建代理對象,該代理對象將同時實現所有接口。運用在這裡,我們就能夠創建一個即是 Iou 類型又是目標接口類型的代理對象,所以它能被安全地從 Iou 類型轉換到目標接口類型並返回。這樣就消除了傳統 IOU 模式下方法返回類型的限制,我們稱此為擴展 IOU 模式。

擴展 IOU 模式的 Java 實現

Java 動態代理的核心是將代理對象上的方法調用統統分派轉發到一個 InvocationHandler 對象上進行處理,為此,我們需要在 RealIouEscrow 基礎再實現一個 InvocationHandler 接口。當用戶調用目標接口的任何方法時,都會自動轉發到 InvocationHandler 接口的 invoke 方法上執行。在 invoke 方法內部,我們可以及時地進行贖回操作以獲得真實結果,然後再通過反射調用相應方法來訪問真實結果的屬性或功能。對調用者而言,進行贖回操作時可能的等待是完全透明的,最終效果完全等價於直接在真實結果上調用某同步方法。RealIouEscrowEx 類實現見清單 7。

清單 7. RealIouEscrowEx 類實現

public class RealIouEscrowEx extends RealIouEscrow implements InvocationHandler
{
   // IOU 結果類的類型對象
   private Class type;

   public RealIouEscrowEx(Class type) throws IllegalArgumentException
   {
     if( type == null || !type.isInterface() )
     {
       throw new IllegalArgumentException("Unsupport non-interface type.");
     }
     this.type = type;
   }

   public Iou issueIou()
   {
     // 返回代理對象,該代理對象同時代理類 Iou 接口類型和結果接口類型
     return (Iou)Proxy.newProxyInstance(Iou.class.getClassLoader(),
       new Class[] {type, Iou.class},
       this);
   }

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
   {
     Object obj;
     if( method.getDeclaringClass() == Iou.class )
     {
       // 如果方法來自於 Iou 類聲明,則將本 IOU 對象設為反射執行的目標對象
       obj = this;
     }
     else
     {
       // 調用非 Iou 類的方法,檢查此 IOU 對象是否已經終止,未終止則保持等待直至終止
       if( !this.closed() )
       {
         this.standBy();
       }
       // 贖回結果對象,並設為反射執行的目標對象
       obj = this.redeem();
     }
     // 在目標對象上執行 invoke 調用
     return method.invoke(obj, args);
   }
}

擴展 IOU 模式帶來了更好的用戶體驗,在使用方法上也有所改進。清單 5 和清單 6 改進後的實現分別是清單 8 和清單 9。

清單 8. 被調方法的實現模型(改進後)

public Foo method( … )
{
   // 首先創建擴展的 escrow 對象 , 指定結果類型為 Foo
   Escrow escrow = new RealIouEscrowEx(Foo.class);

   // 啟動異步服務,並關聯擴展 escrow 對象
   ……

   // 發行 escrow 發行的 Iou 欠條,這裡可以安全的類型轉換到 Foo 再返回
   return (Foo)escrow.issueIou();
}

清單 9. 調用者的實現模型(改進後)

// 調用 method 方法,獲得 Foo 對象(其實是一
   // 個同時代理了 Iou 接口和 Foo 接口的代理對象)
   Foo foo = method();

   // 執行其他事務
   ……

   // 可以直接在 foo 上調用 bar,效果完全等
   // 價於在真正的返回對象上調用 bar 方法
   foo.bar()

   …… 

實例演示

接下來通過一個實例來演示 IOU 設計模式的實際應用,例子描述了一位女管家如何通過 IOU 模式來更加有效地處理家務的故事。

涉及的接口有:頂層接口 Processable 及其子接口 Clothes 和 Food。Processable 接口聲明了 process 方法,子接口 Food 聲明了 addSpice 方法。Clothes 經過清洗(process)變得干淨;Food 經過烹饪(process)變得可食用,而且 Food 還能夠添加調味香料(addSpice)。具體實現類為 ChothesImpl 和 FoodImpl。

涉及的異步服務類是 AsyncService,它以異步方式處理 Processable 對象並調用其 process 方法,並且最後會終止 Escrow 對象以結束 Iou 債務。實例中的 AsyncService 是以後台線程為載體,但是實際應用中用戶可以選擇任意的異步機制。

最後的女管家類是 HouseKeeper。她需要進行的家務包括洗衣、做飯及其他,其中可以並行執行是洗衣和做飯,因為有洗衣機和電飯煲可以幫忙,剩下的則必須一件一件地進行。具體實現見清單 10。

清單 10. HouseKeeper 類

public class HouseKeeper
{
   public static void main(String args[])
   {
     // 初始化待處理的衣服和食物對象
     Clothes clothesToWash = new ClothesImpl();
     Food foodToCook = new FoodImpl();

     // 設定洗衣事務
     Iou iou = wash(clothesToWash);
     // 繼續做其他事情
     doSomethingOther();
     // 設定烹饪事務
     Food foodCooked = cook(foodToCook);
     // 繼續做其他事情
     doSomethingOther();
     // 開始享用食物
     eat(foodCooked);
     // 開始晾曬衣服
     hangout(iou);
   }

   private static Iou wash(Clothes clothes)
   {
     logger("Schedule a task to wash " + clothes);
     // 構造 Escrow 對象
     Escrow escrow = new RealIouEscrow();
     // 啟動後台洗衣服務
     AsyncService service = new AsyncService("wash clothes", clothes, escrow);
     service.start();
     // 隨即通過 Escrow 對象發行一個傳統的 Iou
     return escrow.issueIou();
   }

   private static Food cook(Food food)
   {
     logger("Schedule a task to cook " + food);
     // 構造擴展 Escrow 對象,並關聯 Food 接口類型
     Escrow escrow = new RealIouEscrowEx(Food.class);
     // 啟動後台烹饪服務
     AsyncService service = new AsyncService("cook food", food, escrow);
     service.start();
     // 隨即通過擴展 Escrow 對象發行一個擴展 Iou
     // 它可以被安全地類型裝換到 Food 類型
     return (Food)escrow.issueIou();
   }

   private static void eat(Food food)
   {
     logger("Be about to eat food...add some spice first...");
     // 演示在擴展 Iou 對象上執行方法(效果等價於在真實結果上調用該方法)
     food.addSpice();
     logger(food + " is eaten.");
   }

   private static void hangout(Iou iou)
   {
     logger("Be about to hang out clothes...");
     // 演示在傳統 Iou 對象上的檢查、等待並贖回結果
     if( !iou.closed() )
     {
       logger("Clothes are not ready, stand by...");
       iou.standBy();
     }
     Object clothes = iou.redeem();
     logger(clothes + " are hung out.");
   }

   ……
}

程序的最終執行輸出見清單 11。

清單 11. 程序輸出

[Mon Sep 14 13:33:41 CST 2009] Schedule a task to wash 'Dirty' clothes
   >>> Starting to wash clothes
[Mon Sep 14 13:33:42 CST 2009] Do something other [442 millis]
[Mon Sep 14 13:33:42 CST 2009] Schedule a task to cook 'Uncooked' food
   >>> Starting to cook food
[Mon Sep 14 13:33:42 CST 2009] Do something other [521 millis]
[Mon Sep 14 13:33:42 CST 2009] Be about to eat food...add some spice first...
   >>> Object is not ready, stand by at calling addSpice()
   <<< Finished wash clothes [1162 millis]
   <<< Finished cook food [889 millis]
   <<< Object is ready, continue from calling addSpice()
   >>> Adding spice...
   <<< Spice is added.
[Mon Sep 14 13:33:43 CST 2009] 'Cooked' food is eaten.
[Mon Sep 14 13:33:43 CST 2009] Be about to hang out clothes...
[Mon Sep 14 13:33:43 CST 2009] 'Clean' clothes are hung out.

來分析一下程序的執行情況:女管家在安排了洗衣事務後,繼續做了 442 毫秒的其他事情,接著她又安排了烹饪事務,完後又做了 521 毫秒的其他事情,然後她打算開始享用食物(IOU 模式的魔力:女管家以為 cook 方法返回的“食物”是已經做好的),當她向食物上添加美味的調味品時,奇妙的事情發生了,擴展的 IOU 模式開始發揮作用,它會發現食物其實沒有真正做好,於是在食物 Iou 對象上保持等待直至其被終止並可贖回(數據顯示烹饪事務實際總耗時 889 毫秒),然後才執行真正的添加調味品動作,之後控制權又回到了女管家(女管家對之前的等待過程渾然不知,因為在她看來僅僅是一個普通的方法調用),女管家最終美美地享用了美味的食物,接著她開始晾曬衣服,這次衣服 Iou 對象的贖回進行得相當順利,因為洗衣事務的確已經順利完成了。在整個過程中,我們看到有若干事務在並行進行,卻只有一個等待過程,而這唯一的等待過程也在 Java 動態代理機制下實現了對女管家的完全透明,這就是融合了動態代理機制後的擴展 IOU 模式的魅力所在。

總結

IOU 模式在幫助提高程序的並發性方面有著非常獨到的作用,而引入了動態代理機制支持的擴展 IOU 模式又融入了更加友好的用戶體驗,兩者相得益彰,可謂珠聯璧合。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved