命令模式和AS3

时间:2012-04-20 14:51:59

标签: actionscript-3 flash design-patterns

好奇的是你们如何解决命令封装问题?您是否为每个命令创建一个单独的类?或者还有另一种方式(没有大量的课程)?

请注意,我在动作脚本3上面临问题。

更新:更准确地说,我想知道如何组织命令相关的机器(例如每个命令的类)。

提前谢谢!

3 个答案:

答案 0 :(得分:9)

命令模式是Java缺乏高阶函数(无法传递对函数的引用)的掩盖。在AS /其他ES语言中使用此模式毫无意义,因为此处不存在此问题。

非常不幸的是,学术界最近使用Java进行CS研究,特别是如果CS不是你的专业。这可以解释这个和其他Java-isms移植到其他语言没有任何批判性分析。

答案 1 :(得分:8)

命令模式是关于分离三个不同类别的对象之间的关注点:

  1. Invoker
  2. 接收器
  3. 命令
  4. 正如wvxvw指出的那样,通常意味着你有一个实现ICommand的TON类,它们只是作为代理来调用 Receiver 上的方法 - 这是必需的对于Java。 随着时间的推移会变得有点难以管理。

    但是让我们看一些代码,我将遵循介绍here命令模式基础知识的示例代码,但为了清晰起见略有简化:

    首先是接收器

    // Receiver
    public interface IStarShip {
        function engage():void;
        function makeItSo():void;
        function selfDestruct():void;
    }
    
    public class Enterprise implements IStarShip {
        public function Enterprise() { }
    
        public function engage():void {
            trace(this, "Engaging");
        }
    
        public function makeItSo():void {
            trace(this, "Making it so");
        }
    
        public function selfDestruct():void {
            trace(this, "Self Destructing");
        }
    }
    

    和调用者:

    // invoker
    public class CaptPicard {
        private var _command:ICommand;
    
        public function CaptPicard() { }
    
        public function set command(cmd:ICommand):void {
            this._command = cmd;
        }
    
        public function issueCommand():void {
    
        }
    }
    

    最后是一些命令:

    // command
    public interface ICommand {
        function execute():void;
    }
    
    public class EngageCommand implements ICommand 
    {
        private var receiver:IStarShip
    
        public function EngageCommand(receiver:IStarShip) {
            this.receiver = receiver;
        }
    
        public function execute():void {
            receiver.engage();
        }
    }
    
    public class SelfDestructCommand implements ICommand 
    {
        private var receiver:IStarShip
    
        public function SelfDestructCommand(receiver:IStarShip) {
            this.receiver = receiver;
        }
    
        public function execute():void {
            receiver.selfDestruct();
        }
    }   
    
    public class MakeItSoCommand implements ICommand 
    {
        private var receiver:IStarShip
    
        public function MakeItSoCommand(receiver:IStarShip) {
            this.receiver = receiver;
        }
    
        public function execute():void {
            receiver.makeItSo();
        }
    }   
    

    我们可以通过以下方式来解决这些问题:

    var enterprise:Enterprise = new Enterprise;
    var picard:CaptPicard = new CaptPicard();
    picard.command = new SelfDestructCommand(enterprise);
    picard.issueCommand();
    

    到目前为止,它与示例代码紧密相关,并且是ActionScript中命令模式的非常标准的实现。但是,现在我们有三个ICommand实现,并且随着接收器可以增加的数量增加,编排模式所需的命令数也会增加。

    如果我们开始检查Command本身,除了告诉接收者做一些工作之外,它并没有做太多其他事情。正如wvxvw暗示的那样,动作脚本中已经有了这样的功能:一流的功能。

    让我们看看可能的实现,以减少你需要浮动的ICommand的实现数量,而不需要改变你的模式。

    让我们说我们制作了一个通用类型的命令,让我们说:

    public class GenericCommand implements ICommand {
    
        private var worker:Function;
    
        public function GenericCommand(worker:Function) {
            this.worker = worker;
        }
    
        public function execute():void {
            this.worker.call();
        }
    }
    

    请注意,我们的新类型仍然实现ICommand,但它不是直接绑定到某个实现,而是接受一个worker来做一些工作。它不会立即执行它,只是坚持它并等待其他东西使它运动。

    然后我们可以将代码转换为这样的代码:

    var enterprise:Enterprise = new Enterprise;
    var picard:CaptPicard = new CaptPicard();
    picard.command = new GenericCommand(function() { enterprise.selfDestruct(); });
    picard.issueCommand();
    picard.command = new GenericCommand(function() { enterprise.engage(); });
    picard.issueCommand();
    picard.command = new GenericCommand(function() { enterprise.makeItSo(); });
    picard.issueCommand();
    

    使用这个GenericCommand,我们可以将所有命令展开到这个新模式中(或者在它们之间混合搭配)。

    但是如果你仔细观察,你可以通过使用它来看到我们通过引用封闭的变量enterprise将一个通用命令紧密耦合到一个IStarShip,如果我们不小心可能更令人担心我们可以创建这些闭包的 ton 。它们都需要在某些时候进行垃圾收集,这可能会影响性能,或者更糟糕的是导致内存泄漏。

    我们可以将这一层更多地分离,使其更具动态性。我们可以使用工厂模式来帮助动态生成命令,而不是直接关闭局部变量enterprise。考虑一下:

    public class StarShipCommandFactory {
        private var worker:Function;
    
        public function StarShipCommandFactory(worker:Function) {
            this.worker = worker;
        }
    
        public function to(receiver:IStarShip):ICommand {
            return new GenericCommand(function() {
                worker.call(undefined, receiver);
            });
        }
    }
    

    然后我们可以使用它来创建命令工厂来创建这些命令。类似的东西:

    var enterpriseA:Enterprise = new Enterprise();
    var enterpriseB:Enterprise = new Enterprise();
    var picard:CaptPicard = new CaptPicard();
    
    var selfDestuctor:StarShipCommandFactory = new StarShipCommandFactory(function(starShip:IStarShip):void {
        starShip.selfDestruct();
    } );
    
    var blowUpA:ICommand = selfDestructor.to(enterpriseA);
    var blowUpB:ICommand = selfDestructor.to(enterpriseB);
    
    picard.command = blowUpA;
    picard.issueCommand();
    
    picard.command = blowUpB;
    picard.issueCommand();
    

    这将减少需要生成的静态类的数量,以支持动态创建的对象,利用属性的第一类函数,但仍然应用相同的一般概念。

    事实上,使用这种模式,您可以构建非常复杂的命令,代理多个接收器上的多个动作,这可能是一个非常强大的功能。

    为什么要使用ICommand的传统实现呢?

    坚持传统模式的最大理由之一是易于序列化。因为对象是显式的,所以它们很容易序列化。所以,假设你有一堆Vector.<ICommand>。您可以轻松地序列化它们,将它们写出来,然后再重新加载它们并按顺序重放它们,并且应该处于与您完全相同的状态。动态生成的对象就像我之前描述的对象那样做起来有点棘手 - 不是不可能,只是比较棘手。

    如果您担心维护大量ICommand映射到接收器操作,那么在过去我使用元编程来解决这个问题。使用ruby / python解析一定数量的文件,并自动生成ICommand映射。它们很多时候都是相当天真的实现,它们是自动化的主要候选者。


    无论如何,这些都是我对这个问题的看法。你可以做很多事情来帮助简化你的代码 - 我刚刚触及了表面。您的里程可能会有所不同 - 请记住:“找到解决问题的模式,而不是反过来” - 但也许您可以从中收集一些有用的信息。

答案 2 :(得分:2)

每个命令使用一个类。为模式为某些问题域提供的灵活性,许多类是一个很小的代价。

使用游戏示例,使用名为GameEntity的自定义类 - 玩家,敌人等 - 我首先定义一个命令界面

ICommand{
    function execute():void;
}

然后,每个命令类在注入构造函数的接收器上实现execute方法:

public class GoRight implements ICommand{

    _gameEntity:GameEntity

    public function GoRight(entity:GameEntity){
        _gameEntity = entity;
    }

    public function execute():void{
        _gameEntity.x++;
    }
}

public class GoLeft implements ICommand{

    _gameEntity:GameEntity

    public function GoLeft(entity:GameEntity){
        _gameEntity = entity;
    }

    public function execute():void{
        _gameEntity.x--;
    }
}
每个命令都有

等等。

如果使用堆栈,其中一个命令在另一个命令完成后调用,则需要添加eventlisteners以监听命令完成作为启动下一个命令的触发器。

然而,恕我直言,如果您发现自己正在构建一个软件,其中命令模式成为一个关键的架构解决方案,我会认真思考动作脚本或Flash游戏是否真的是正确的工具。对于任务排队和长命令链的执行/撤消,并发 - Flash不提供的东西 - 对于良好的用户体验变得很重要。那当然是我的意见......