コマンド
本稿およびこのシリーズの最後を飾るパターンはコマンドパターンです。従来のオブジェクト指向プログラミングのコンテキストの中では、このパターンは、標準から大きく外れています。通常は、オブジェクトは何らかの名詞を表すものですが(よって「オブジェクト」と呼ばれる)、このコマンドパターンでは、オブジェクトは動詞を表します。オブジェクトの役目は、メソッドの呼び出しをカプセル化することです。コマンドパターンとは、メソッドとそのメソッドを呼び出したいオブジェクトを実装するオブジェクト間の抽象層にすぎません。これは、オブジェクト指向言語が従来型であるほど便利であり、ユーザーインターフェイスに最もよく使用されます。JavaScriptでは、コマンドオブジェクトは単なる関数であることもあり、この関数はコードの単純化に大きく貢献します。
アラーム時計のコマンドパターン化
ここでは、従来のコマンドパターンの使い方をわかりやすく説明します。関係のないコードと混同せずにコマンドパターンの動作を示すために、この例では非常にシンプルなコードを使用します。この例はアラーム時計です。
var EnableAlarm = function(alarm) {
this.alarm = alarm;
}
EnableAlarm.prototype.execute = function () {
this.alarm.enable();
}
var DisableAlarm = function(alarm) {
this.alarm = alarm;
}
DisableAlarm.prototype.execute = function () {
this.alarm.disable();
}
var SetAlarm = function(alarm) {
this.alarm = alarm;
}
SetAlarm.prototype.execute = function () {
this.alarm.set();
}
このコードでは、少なくとも、
enable、disable、setという3つのメソッドを持つアラームオブジェクトが既に作成済みであると仮定しています。これらの3つのメソッドをカプセル化するために3つの異なるクラスを作成しました。各クラスは、この場合はexecuteメソッドのみを持つ特定のインターフェイスを採用しています。
次のコードには
Buttonクラスがあり、ボタンの文字列ラベルとコマンドオブジェクトという2つの引数を受け取ります。このコマンドオブジェクトのexecuteメソッドは、ボタンが押されたときに呼び出されます。本質的には、単に、UI要素へのバインディングイベントに対して従来のオブジェクト指向のアプローチを採用しているだけです。var alarms = [/* array of alarms */],
i = 0,
len = alarms.length;
for (; i < len; i++) {
var enable_alarm = new EnableAlarm(alarms[i]),
disable_alarm = new DisableAlarm(alarms[i]),
set_alarm = new SetAlarm(alarms[i]);
new Button('Enable', enable_alarm);
new Button('Disable', disable_alarm);
new Button('Set', set_alarm);
}
これはアラームのリストで、各アラームは制御用のボタンを必要としています。そこで、各アラーム用に3つのコマンドオブジェクトを作成し、各アラームの3つのボタンにパラメーターとして送信します。この例では、JavaScriptの柔軟性により、すべてのコマンドオブジェクトを数行のコードに短くまとめることができます。
var makeCommand = function( object, methodName ) {
return {
execute: function() {
object[methodName]();
}
}
}
// This is how it's used now:
var alarms = [/* array of alarms */],
i = 0, len = alarms.length;
for (; i < len; i++) {
var enable_alarm = makeCommand(alarms[i], 'enable'),
disable_alarm = makeCommand(alarms[i], 'disable'),
set_alarm = makeCommand(alarms[i], 'set');
new Button('enable', enable_alarm);
new Button('disable', disable_alarm);
new Button('set', set_alarm);
}
特定のタイプのオブジェクトではなく、
executeメソッドを備えたオブジェクトリテラルを返します。重要なのは、どちらの場合も、同じインターフェイスが使用されるということです。それでもまだ、成果の割に作業が多すぎるようです。シンプルなコールバック関数を使用して書き換えると、次のようになります。var alarms = [/* array of alarms */],
i = 0, len = alarms.length;
for (; i < len; i++) {
new Button('enable', alarm.enable);
new Button('disable', alarm.disable);
new Button('set', alarm.set);
}
ただし、問題が1つあります。これらのコールバック関数を実行したとき、コンテキストが失われ、
thisがアラームを参照しなくなり、大きな障害となります。そこで、コンテキスト変数を取るButtonコンストラクターを作成するか、アラームメソッドのプロキシを作成するか(ほとんどの場合コマンドパターンではこの方法を取る)、メソッド内でのアラームを参照するselfやthatなどを使用できるようにクロージャを持つAlarmコードを書き直す必要があります。お気づきのように、既存のクラスに変更を加えずに済む唯一の方法は、プロキシまたはアラームとボタン間に別種の抽象層を使用するやり方だけです。コード行は増えますが、この方法は非常によい選択肢です。問題の単純さ
この例における最も重要なポイントは、前にも述べましたが、これがコマンドパターンの仕組みを示すことを目的とした比較的シンプルな例であり、本来の性能や利便性を十分に表せてはいないということです。コマンドパターンは、もっとずっと便利に使用できます。上の例では、
setメソッドは、アラームが設定する内容を把握できるよう、パラメーターをいくつか受け取ります。この場合、コマンドオブジェクトはパラメーターの特定に責任を負い、パラメーターをalarm.setに渡します。
他にコマンドパターンの使用が役立つ状況としては、
alarmまたはその他のオブジェクトに対して複数のメソッドの呼び出しを行う場合などがあります。基本的に、execute関数が少しでも複雑になると、コマンドオブジェクトがより有用になります。
最後に、コマンドパターンが最も役に立つ状況は、
executeやundoなどの関数が2つ以上含まれている場合です。この場合は、execute関数が実行した任意のアクションを取り消すためのundo関数を作成できます。この関数を活用するには、ボタンがコマンドオブジェクトに対してexecuteメソッドを呼び出したときに、それ以前にexecuteメソッドを呼び出していた他のコマンドオブジェクトのスタックに、そのコマンドオブジェクトを積み重ねます。ユーザーがCtrl + Zキーを押して直前のアクションを取り消そうとした場合、スタックからコマンドオブジェクトが引き出され、そのundoメソッドが呼び出されます。このように使用すると、コマンドパターンは上の例よりもずっと便利でパワフルです。コマンドの使用
コマンドパターンがこの記事の締めくくりです。コマンドパターンについてもう少し詳しく知りたい場合、またはJavaScriptにおけるコマンドオブジェクトの作成の別の方法について具体的に知りたい場合は、筆者のJavaScriptブログの「JavaScript Design Patterns: Command」の記事をお読みください。
0 件のコメント:
コメントを投稿