4

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

请注意,我在操作脚本 3 上遇到了问题。

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

先感谢您!

4

3 回答 3

9

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

很不幸,如今学术界使用 Java 进行 CS 研究,尤其是如果您的专业不是 CS 的话。这将解释这个和其他 Java 主义在没有任何批判性分析的情况下被移植到其他语言中。

于 2012-04-20T15:34:47.633 回答
8

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

  1. 祈求
  2. 接收者_
  3. 命令_

正如 wvxvw 所指出的那样,这通常意味着您有大量实现ICommand这些类的类实际上只是作为代理来调用接收器上的方法——这是 Java 所必需的。随着时间的推移,这可能会变得有点难以管理。

但是让我们看一些代码,我将按照示例代码介绍此处找到的命令模式的基础知识,但为了清楚起见稍微简化了:

所以首先是接收器

// 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实现,并且随着接收者可以做的事情的数量增加,编排模式所需的命令数量也会增加。

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

让我们看一下可能的实现,以减少您需要浮动的 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,但它不是直接绑定到某个实现,而是接受一个工作人员来做一些工作。它不会立即执行它,它只是抓住它并等待其他东西启动它。

然后我们可以把我们的代码改成这样:

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,我们可以将所有命令展开到这个新模式中(或在它们之间混合和匹配)。

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

我们可以将这一层进一步解耦,使其更具动态性。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映射到接收器操作的 s,那么过去我使用元编程来解决这个问题。使用 ruby​​/python 解析一定数量的文件,并自动生成ICommand映射。很多时候,它们是相当幼稚的实现,是自动化的主要候选者。


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

于 2012-04-24T16:27:20.703 回答
2

我每个命令使用一个类。对于该模式为某些问题域提供的灵活性,大量的类是一个很小的代价。

使用一个游戏示例,带有一个名为 GameEntity 的自定义类 - 玩家、敌人等 - 我将首先定义一个命令接口

ICommand{
    function execute():void;
}

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

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--;
    }
}

等等每个命令。

如果您使用堆栈,其中一个命令在另一个命令完成后被调用,您需要添加事件侦听器来侦听命令完成作为启动下一个命令的触发器。

但是恕我直言,如果您发现自己构建了一个命令模式成为关键架构解决方案的软件,我会认真考虑 actionscript 还是 Flash 播放器是否真的是正确的工具。对于任务队列和长命令链的执行/撤消,并发性(Flash 不提供的东西)对于良好的用户体验变得很重要。当然这只是我的看法...

于 2012-04-24T13:46:04.590 回答