Egret应用开发实践(03) MVC 模块化具体实现

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

    原文作者:guyoung
    原文地址: https://segmentfault.com/a/1190000004590020
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞