コマンドのカプセル化の問題をどのように解決しますか。コマンドごとに個別のクラスを作成していますか? それとも別の方法がありますか (大量のクラスなし)?
アクション スクリプト 3 の問題に直面していることに注意してください。
Upd:より正確には、コマンド関連の機構 (コマンドごとのクラスなど) を整理する方法を知りたいです。
前もって感謝します!
コマンドのカプセル化の問題をどのように解決しますか。コマンドごとに個別のクラスを作成していますか? それとも別の方法がありますか (大量のクラスなし)?
アクション スクリプト 3 の問題に直面していることに注意してください。
Upd:より正確には、コマンド関連の機構 (コマンドごとのクラスなど) を整理する方法を知りたいです。
前もって感謝します!
コマンド パターンは、Java の高次関数の欠如 (関数への参照を渡すことができない) のカバーアップです。この問題は存在しないため、AS / 他の ES 言語でこのパターンを使用しても意味がありません。
最近の学界が、特に CS が専攻ではない場合、CS の研究に Java を使用していることは非常に残念です。これは、これと、批判的な分析なしで他の言語に移植された他の Java-ism を説明するでしょう。
コマンド パターンは、オブジェクトの 3 つの異なるカテゴリ間の懸念を分離することに関するものです。
wvxvw が指摘したように、これは通常、 ReceiverICommand
でメソッドを呼び出すためのプロキシとして実際に機能する実装クラスのトンがあることを意味します。これは 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 でのコマンド パターンのかなり標準的な実装です。しかし、現時点では 3 つの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
を使用すると、すべてのコマンドをこの新しいパターンに展開できます (またはそれらを組み合わせて一致させることができます)。
しかし、よく見ると、これを使用することで、closed-over variable を参照することで汎用コマンドを IStarShip に密結合していることがわかります。enterprise
おそらくさらに心配なのは、注意を怠ると、これらを大量に作成する可能性があることです。閉鎖。それらはすべて、ある時点でガベージ コレクションを行う必要があり、パフォーマンスに影響を与えたり、メモリ リークを引き起こしたりする可能性があります。
その 1 つのレイヤーをさらに切り離して、さらに動的にすることができます。ローカル変数を直接閉じるのではなく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
thenの従来の実装を使用する理由は何ですか?従来のパターンに固執する最大の理由の 1 つは、シリアル化の容易さです。オブジェクトは明示的であるため、シリアル化が非常に簡単になります。それで、あなたがのスタックを持っていたとしましょうVector.<ICommand>
。それらを簡単にシリアル化して書き出し、後で再ロードして順番に再生することができ、元の状態とまったく同じ状態になるはずです。前に説明したような動的に生成されたオブジェクトは、それを行うのが少しトリッキーです。不可能ではなく、トリッキーなだけです。
受信者のアクションにマップする膨大な数の を維持することに懸念がある場合はICommand
、過去にメタプログラミングを使用してこの問題を解決しました。ruby/python を使用して、設定された数のファイルを解析し、ICommand
マッピングを自動的に生成します。多くの場合、それらはかなり単純な実装であり、自動化の最有力候補です。
とにかく、それはこの主題に関する私の考えです。コードを単純化するためにできることは他にもたくさんあります。ここでは表面をなぞっただけです。「問題を解決するパターンを見つけてください。その逆ではありません」ということを覚えておいてください。
コマンドごとに 1 つのクラスを使用します。多数のクラスは、特定の問題領域に対してパターンが提供する柔軟性に対して支払う小さな代償です。
ゲームの例を使用して、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--;
}
}
など、すべてのコマンドについて。
1 つのコマンドが終了した後に別のコマンドが呼び出されるスタックを使用する場合は、次のコマンドを開始するためのトリガーとしてコマンドの完了をリッスンするイベントリスナーを追加する必要があります。
ただし、コマンド パターンが主要なアーキテクチャ ソリューションとなるソフトウェアを構築している場合は、アクション スクリプトまたは Flash プレーヤーが本当に適切なツールであるかどうかをよく考えます。長いコマンド チェーンのタスク キューイングと do/undo では、同時実行性 (Flash では提供されない機能) が優れたユーザー エクスペリエンスにとって重要になります。もちろん、それは私の意見です...