PureMVC源碼分析


PureMVC 是在基於模型、視圖和控制器 MVC 模式建立的一個輕量級的開源應用框架,具有跨平台語言無關性。最初被應用於adobe flex,actionScript開發中,現已被移植到包括c++,java,c#,php等主要語言平台上,在各平台上的實現方式也幾乎一樣,降低了用戶學習成本。

本文從PureMVC actionScript版源碼角度分析PureMVC如何工作及它如何有效降低各模塊間耦合度。了解PureMVC如何工作需要對其源代碼進行分析,源代碼可在http://trac.puremvc.org/PureMVC_AS3下載。

一,PureMVC如何工作:

首先來看一張類圖:

從圖中可以看到在PureMvc中最主要的類僅僅只有Model,View,Controller,Proxy,Mediator,Command,Facade而已。學習PureMVC之初,需要了解如下一些基本事實:

Model->Proxy,Model保存了所有Proxy對象的引用,Proxy負責數據的讀取與存儲,具體到actionScript可以是Loader加載的數據,Socket收取的數據等。

View->Mediator,View保存了所有Mediator對象的引用,Mediator對象操作具體的視圖組件,例如flex中的DataGrid,Input等,它負責在視圖組件上監聽特定事件和更新視圖組件。

Controller->Command,Controller保存了所有Command對象的引用,Command又分為SimpleCommand(簡單)和MacroCommand(復雜),應用程序的業務邏輯都是在Command中實現。

在as3中,上述三種映射關系是通過Array實現的,其中Proxy,Mediator及Command又分別通過Dictionary鍵值對形式保存了各自名稱和實例的映射關系。具體來看個Controller->Command的例子。

在Controller類中,有這一句

protected var commandMap : Array;

 

 那么,注冊command后,必定是保存commandMap數組中了。代碼果然是這樣的,注意方法體中最后一句:

public function registerCommand( notificationName : String, commandClassRef : Class ) : void
        {
            if ( commandMap[ notificationName ] == null ) {
                view.registerObserver( notificationName, new Observer( executeCommand, this ) );
            }
            commandMap[ notificationName ] = commandClassRef;
        }

 從方法的兩個參數我們還可以猜到特定notification和command是通過通知名和命令類引用方式一一對應的。那么自然可以進一步聯想到PureMVC事件機制中所謂的事件通知只不過是通過特定notificationName檢索到相應command,然后執行command中的某個方法,那么registerCommand方法就如同observer/publiser模式中訂閱事件的過程。

下面就遵循PureMVC的設計思路,來追蹤一遍“observer訂閱事件->publiser發送事件->observer執行監聽方法”的過程。訂閱事件已經講過了,現在從事件發送講起。

通過調用Facade中的sendNotification方法,就可以通知之前注冊的command去執行了:-)

public function sendNotification( notificationName:String, body:Object=null, type:String=null ):void 
        {
            notifyObservers( new Notification( notificationName, body, type ) );
        }
public function notifyObservers ( notification:INotification ):void 
        {
            if ( view != null ) view.notifyObservers( notification );
        }

 

可以看到,實際上執行的是View的notifyObservers方法,該方法體如下:

public function notifyObservers( notification:INotification ) : void
    {
            if( observerMap[ notification.getName() ] != null ) {
                
                // Get a reference to the observers list for this notification name
                var observers_ref:Array = observerMap[ notification.getName() ] as Array;

                // Copy observers from reference array to working array, 
                // since the reference array may change during the notification loop
                   var observers:Array = new Array(); 
                   var observer:IObserver;
                for (var i:Number = 0; i < observers_ref.length; i++) { 
                    observer = observers_ref[ i ] as IObserver;
                    observers.push( observer );
                }
                
                // Notify Observers from the working array                
                for (i = 0; i < observers.length; i++) {
                    observer = observers[ i ] as IObserver;
                    observer.notifyObserver( notification );
                }
            }
    }

 

可以看到每一個notification都相應的在View中的observerMap數組中保存了一份觀察者列表。由於同一個nofication是可以被多個observer監聽的,因此觸發時必須通知到每一個觀察者,這是通過調用observer的notifyObserver方法做到的。在Observer類中可以找到該方法,定義如下:

public function notifyObserver( notification:INotification ):void
        {
            this.getNotifyMethod().apply(this.getNotifyContext(),[notification]);
        }

 

這里只是簡單調用了一個函數,先來看看Observer類構造函數結構:

public function Observer( notifyMethod:Function, notifyContext:Object ) 
        {
            setNotifyMethod( notifyMethod );
            setNotifyContext( notifyContext );
        }
public function setNotifyMethod( notifyMethod:Function ):void
        {
            notify = notifyMethod;
        }
        
public function setNotifyContext( notifyContext:Object ):void
        {
            context = notifyContext;
        }

 

notifyMethod和notifyContext在構造函數中被傳入,再來回顧下前面已提過的注冊命令的registerCommand方法:

public function registerCommand( notificationName : String, commandClassRef : Class ) : void
        {
            if ( commandMap[ notificationName ] == null ) {
                view.registerObserver( notificationName, new Observer( executeCommand, this ) );
            }
            commandMap[ notificationName ] = commandClassRef;
        }

 

所謂的注冊命令不過就是針對某個特定的notification注冊了一個observer,每個通知對應一個觀察者,當該通知廣播時就會執行相應觀察者的notifyMethod方法。這里要注意的是構造Observer實例的過程中將this對象傳入是為了在調用Observer實例的notifyMethod方法時獲得正確的函數運行時上下文。再來深究下executeCommand方法,

public function executeCommand( note : INotification ) : void
        {
            var commandClassRef : Class = commandMap[ note.getName() ];
            if ( commandClassRef == null ) return;

            var commandInstance : ICommand = new commandClassRef();
            commandInstance.execute( note );
        }

 

容易看出只是從Controller的commandMap關聯數組中檢索出key為notificationName的相應command,然后執行該command的execute方法。

在PureMVC中,Command的實現有兩種,分別是SimpleCommand和MacroCommand,兩者都有一個execute實例方法,無本質區別。本文以SimpleCommand為例講解,該類定義如下:

public class SimpleCommand extends Notifier implements ICommand, INotifier 
    {
        public function execute( notification:INotification ) : void
        {
            
        }                      
    }

 

可以看到該類只是簡單聲明了execute方法,方法體則留待派生類去實現。由此可以看出利用PureMVC工作時,無非就是監聽事件和廣播事件的過程。PureMVC已經搭起了程序運轉架構,我們自己要做的工作只是在execute方法中實現程序的業務邏輯。事件監聽->觸發->反應的過程歸納起來無非就是:

1.定義派生自SimpleCommand或MacroCommand的Command類並實現其execute方法;

2.調用registerCommand方法為某個特定的notification指定一個監聽其廣播事件的Command類;

3.調用sendNotification通知相應Command執行

 

當然,如果你認為針對每個notification都實現一個command會導致類過多的話,也可以通過調用Facade類registerMediator方法來達到同樣的監聽目的。流程是這樣的:

public function registerMediator( mediator:IMediator ):void 
        {
            if ( view != null ) view.registerMediator( mediator );
        }
public function registerMediator( mediator:IMediator ) : void
        {
            // do not allow re-registration (you must to removeMediator fist)
            if ( mediatorMap[ mediator.getMediatorName() ] != null ) return;
            
            // Register the Mediator for retrieval by name
            mediatorMap[ mediator.getMediatorName() ] = mediator;
            
            // Get Notification interests, if any.
            var interests:Array = mediator.listNotificationInterests();

            // Register Mediator as an observer for each of its notification interests
            if ( interests.length > 0 ) 
            {
                // Create Observer referencing this mediator's handlNotification method
                var observer:Observer = new Observer( mediator.handleNotification, mediator );

                // Register Mediator as Observer for its list of Notification interests
                for ( var i:Number=0;  i<interests.length; i++ ) {
                    registerObserver( interests[i],  observer );
                }            
            }
            
            // alert the mediator that it has been registered
            mediator.onRegister();
        }

簡而言之,就是:

1. 在Mediator的listNotificationInterests方法中列出其感興趣的notification;

2.調用Facade類的registerMediator方法中會注冊以Mediator的handleNotification方法為notifyMethod的Observer監聽特定notification;

3.在Mediator的handleNotification方法中實現業務邏輯

以registerMediator的方式注冊和派發事件固然能減少Command類的個數,但也存在一個弊端。即當一個Mediator監聽多個Notification時,不得不在handleNotification逐個判斷Notification,然后調用相應的實現。這樣會造成if...else if...else或switch...case分支過多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM