Egret應用開發實踐(03) MVC 模組化具體實現

guyoung發表於2019-02-16

PureMVC框架的目標很明確,即把程式分為低耦合的三層:Model、View和Controller,這三部分由三個單例模式類管理,分別是Model、View和Controller,三者合稱為核心層或核心角色。

Model層

Model層儲存對Proxy物件的引用,Proxy負責運算元據模型,與遠端服務通訊存取資料。這樣保證了Model層的可移植性。

Model通過使用Proxy來保證資料的完整性、一致性。Proxy集中程式的Domain Logic(域邏輯),並對外公佈運算元據物件的API。它封裝了所有對資料模型的操作,不管資料是客戶端還是伺服器端的,對程式其他部分來說就是資料的訪問是同步還是非同步的。

Proxy物件不應該通過引用、操作Mediator物件來通知系統它的Data Object(資料物件)發生了改變。它應該採取的方式是傳送Notification(這些Notification可能被Command或Mediator響應)。Proxy不關心這些Notification被髮出後會影響到系統的什麼。

把Model層和系統操作隔離開來,這樣當View層和Controller層被重構時就不會影響到Model層。

gameProxy.ts

///<reference path="../../../../../typings/main.d.ts"/>
export class GameProxy extends puremvc.Proxy {
    public static NAME: string = `GAME_PROXY`;
    private _life: number = 0;
    constructor() {
        super(GameProxy.NAME);
    }
    public onRegister() {
        this._life = 10;
    }
    public getLife () {
        return this._life;
    }
    public setLife(life) {
        this._life = life;
    }
    public incLife(cb) {
        this._life++;
    }
    public decLife(cb) {
        this._life--;
        if (this._life <= 0) {
            this.sendNotification(`GameOver`);
        } else {
            if (cb) {
                cb(this._life);
            }
        }
    }
}

View層

View儲存對Mediator物件的引用。由Mediator物件來操作具體的檢視元件包括:新增事件監聽器,傳送或接收Notification ,直接改變檢視元件的狀態。這樣做實現了把檢視和控制它的邏輯分離開來。

當用View註冊Mediator時,Mediator的listNotifications方法會被呼叫,以陣列形式返回該Mediator物件所關心的所有Notification。之後,當系統其它角色發出同名的Notification(通知)時,關心這個通知的Mediator都會呼叫handleNotification方法並將Notification以引數傳遞到方法。

Mediator是檢視元件與系統其他部分互動的中介,偵聽View Component來處理使用者動作和Component的資料請求。Mediator通過傳送和接收Notification來與程式其他部分通訊。

Mediator儲存了一個或多個View Component的引用,通過View Component自身提供的API管理它們。一個View Component應該把儘可能自己的狀態和操作封裝起來,對外只提供事件、方法和屬性的簡單的API。

Mediator的主要職責是處理View Component派發的事件和系統其他部分發出來的Notification(通知)。因為Mediator也會經常和Proxy互動,所以經常在Mediator的構造方法中取得Proxy例項的引用並儲存在Mediator的屬性中,這樣避免頻繁的獲取Proxy例項。

Mediator負責處理與Controller層、Model層互動,在收到相關Notification時更新View Component。

gameMainScene.ts

///<reference path="../../../../../typings/main.d.ts"/>
export class GameMainScene extends egret.Sprite {
    public onchange: any;
    public onKill: any;
    private _lifeText: egret.TextField;
    public constructor(width: number, height: number) {
        super();
        this.width = width;
        this.height = height;
        this.init();
    }
    private init() {  
        var text = new egret.TextField();
        text.text = `Game Main Scene `;
        text.x = (this.width - text.width) * 0.5;
        text.y = 200;
        this.addChild(text);
        this._lifeText = new egret.TextField();       
        this._lifeText.x = (this.width - this._lifeText.width) * 0.5;
        this._lifeText.y = 250;
        this.addChild(this._lifeText);
        var btn = new egret.TextField();
        btn.text = `KILL`;
        btn.x = (this.width - btn.width) * 0.5;;
        btn.y = 300;
        btn.touchEnabled = true;
        this.addChild(btn);
        btn.addEventListener(egret.TouchEvent.TOUCH_TAP, function(e: egret.TouchEvent) {
            if (this.onKill) {
                this.onKill();
            }
        }, this);
        btn = new egret.TextField();
        btn.text = `EXIT`;
        btn.x = this.width - 20 - btn.width;
        btn.y = this.height - 20 - btn.height;
        btn.touchEnabled = true;
        this.addChild(btn);
        btn.addEventListener(egret.TouchEvent.TOUCH_TAP, function(e: egret.TouchEvent) {
            if (this.onKill) {
            this.onchange(`Menu`);
        }
        }, this);
    }
    public showLife(life) {
        this._lifeText.text = "LIFE:" + life;
    }
}

gameMainSceneMediator.ts

///<reference path="../../../../../typings/main.d.ts"/>
import {GameMainScene} from `../scenes/gameMainScene`;
import {GameProxy} from `../../model/proxy/gameProxy`;
export class GameMainSceneMediator extends puremvc.Mediator {
    public static NAME: string = `GAME_MAIN_SCENE_MEDIATOR`;
    private _gameProxy: any;
    constructor() {
        super(GameMainSceneMediator.NAME);
    }
    public listNotificationInterests(): string[] {
        return [];
    }
    public handleNotification(notification: puremvc.INotification): void {
    }
    public onRegister(): void {
        this._gameProxy = this.facade.retrieveProxy(GameProxy.NAME);
    }
    public onRemove(): void {
    }   
    public renderScene(width: number, height: number): void {
        var self = this;
        self.viewComponent = new GameMainScene(width, height);
        self.viewComponent.onchange = function(e) {
            self.sendNotification(e);
        }
        self.viewComponent.life = self._gameProxy.getLife();
        self.viewComponent.onKill = function() {
            var showLife = function(life) {
                self.viewComponent.showLife(life)
            };
            self._gameProxy.decLife(showLife);
        };
    }
    public destroyScene() {
        this.viewComponent = null;
    }
}

Controller層

Controller儲存所有Command的對映。

Command物件是無狀態的;只有在需要的時候(Controller收到相應的Notification)才會被建立,並且在被執行(呼叫execute方法)之後就會被刪除。所以不要在那些生命週期長的物件(long-living object)裡引用Command物件。

Command可以獲取Proxy物件並與之互動,傳送Notification,執行其他的Command。經常用於複雜的或系統範圍的操作,如應用程式的“啟動”和“關閉”。應用程式的業務邏輯應該在這裡實現。

Controller會註冊偵聽每一個Notification,當被通知到時,Controller會例項化一個該Notification對應的Command類的物件。最後,將Notification作為引數傳遞給execute方法。

Command要實現ICommand介面。在PureMVC中有兩個類實現了ICommand介面:SimpleCommand、MacroCommand。SimpleCommand只有一個execute方法,execute方法接受一個Inotification例項做為引數。實際應用中,你只需要重寫這個方法就行了。MacroCommand讓你可以順序執行多個Command。每個執行都會建立一個Command物件並傳參一個對源Notification的引用。

MacroCommand在構造方法呼叫自身的initializeMacroCommand方法。實際應用中,你需重寫這個方法,呼叫addSubCommand新增子Command。你可以任意組合SimpleCommand和MacroCommand成為一個新的Command。

gameMainCommad.ts

///<reference path="../../../../../typings/main.d.ts"/>
export class GameMainCommad extends puremvc.SimpleCommand {
    public static NAME: string = `GAME_MAIN_COMMAD`;
    constructor() {
        super();
    }
    public execute(notification: puremvc.INotification): void {
        this.sendNotification(`CHANGE_SCENE`, `GAME_MAIN_SCENE_MEDIATOR`);
    }
}

startupCommand.ts

///<reference path="../../../../../typings/main.d.ts"/>
import {PrepModelCommand} from `./prepModelCommand`;
import {PrepViewCommand} from `./prepViewCommand`;
import {PrepControllerCommand} from `./prepControllerCommand`;
export class StartupCommand extends puremvc.MacroCommand {
    public static NAME: string = `STARTUP_COMMAND`;
    constructor() {
        super();
    }
    public initializeMacroCommand(): void {            
        this.addSubCommand(PrepModelCommand);
        this.addSubCommand(PrepViewCommand);
        this.addSubCommand(PrepControllerCommand);
    }
}

其他

參考


Guyoung Studio

相關文章