好奇的是你们如何解决命令封装问题?您是否为每个命令创建一个单独的类?或者还有另一种方式(没有大量的课程)?
请注意,我在动作脚本3上面临问题。
更新:更准确地说,我想知道如何组织命令相关的机器(例如每个命令的类)。
提前谢谢!
答案 0 :(得分:9)
命令模式是Java缺乏高阶函数(无法传递对函数的引用)的掩盖。在AS /其他ES语言中使用此模式毫无意义,因为此处不存在此问题。
非常不幸的是,学术界最近使用Java进行CS研究,特别是如果CS不是你的专业。这可以解释这个和其他Java-isms移植到其他语言没有任何批判性分析。
答案 1 :(得分:8)
命令模式是关于分离三个不同类别的对象之间的关注点:
正如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不提供的东西 - 对于良好的用户体验变得很重要。那当然是我的意见......