Java 開発者にとっての Dojo の概念

クラスの宣言とコンテキストの設定

Web ベースのアプリケーションで Dojo がますます使われるようになっています。多くの開発者は Java™ プログラミングの高いスキルを持っていますが、JavaScript に関しては限られた経験しかありません。彼らは、強い型付けのオブジェクト指向のコンパイラー言語から動的で弱い型付けのスクリプト言語に移行する際の概念の飛躍に悪戦苦闘しているかもしれません。こうした概念の混乱があるため、開発者にとって Dojo クラスを正しく宣言するのが困難になる可能性があります。この記事では、この混乱を整理し、なぜコンテキストを設定することが必要なのか、そしてコンテキストをどう使えばよいかを説明します。

Dave Draper, WebSphere Application Server Administrative Console Developer, WSO2 Inc

Photo of Dave DraperDave Draper はこの 6 年間、WebSphere Application Server Administrative Console の開発業務に携わってきました。彼は Sun 認定 Web コンポーネントディベロッパであり、Web ベースのツールの開発を幅広く経験してきています。



2008年 10月 14日

はじめに

JavaScript を使用した経験がほとんど、あるいはまったくない状態で Dojo を手にした Java プログラマーは、Dojo を利用できるようにするためのいくつかの概念に悪戦苦闘することになります。Dojo に関する主な懸念は (この記事の執筆時点では)、Dojo がまだ初期段階であり (バージョン 1.0 がようやく 2008年2月にリリースされたばかりです)、入手可能なドキュメントがまだある程度限定されてしまうという点です。この記事は、Dojo ツールキットをすぐに理解してアプリケーション開発で使えるように、Java コードから Dojo へ移行するための橋渡しをします。

Dojo ツールキットの入手方法や Dojo ツールキットを使うために必要なステートメントに関する情報は他でも豊富に提供されているため、この記事ではそうした内容は説明しません。この記事はサーブレット開発を経験してきた Web 開発者を対象としています。

JavaScript のハッシュ

Dojo を扱う際の最初の難関は、Dojo の関数を呼び出す際に使われる構文を理解すること、特に「ハッシュ」、つまり JavaScript オブジェクトの使い方を理解することです。ハッシュは、カンマで区切られた一連の属性を中括弧に囲んで表現します。簡単な例をリスト 1 に示します。この例では、6 つの属性 (ストリング、整数、ブール値、未定義の属性、別のハッシュ、関数) で構成されるハッシュを宣言しています。

リスト 1. JavaScript のハッシュの例
var myHash = {
    str_attr : "foo",
    int_attr : 7,
    bool_attr : true,
    undefined_attr : null,
    hash_attr : {},
    func_attr : function() {}
};

JavaScript は弱い型付けの言語だということを忘れないことが重要です。つまり各属性はその属性の名前に関連する値に初期化されていますが、だからといって最初の str_attr 属性を後で整数またはブール値 (あるいは任意の他の型) に設定できない、ということはありません。ハッシュの中の各属性へのアクセスや設定はドット演算子を使って行うことができます (リスト 2)。

リスト 2. ハッシュの属性へのアクセスと設定
// Accessing a hash attribute...
console.log(myHash.str_attr);

// Setting a hash attribute...
myHash.str_attr = "bar";

myHash の最初の 4 つの属性は自明のはずです。また、ハッシュの属性にハッシュが含まれていることは驚くにはあたりません。(これは Java クラスが基本型とオブジェクト型の両方を参照することに相当すると考えることができます)。そして、理解する必要がある最も重要な属性は、最後の属性です。

関数はオブジェクト

Java コードには java.reflection.Method クラスがありますが、このクラスは基本的にメソッドに対するラッパーとして動作します。JavaScript では関数はオブジェクトであり、他の関数への引数として設定、参照され、渡される他のオブジェクトとまったく同じです。関数呼び出しの中で新しい関数を宣言しなければならないことがよくありますが、これは Java のメソッド呼び出しの中で匿名内部クラスを宣言する方法とほとんど同じです。

Java のメソッドと JavaScript の関数のもう 1 つの重要な違いは、JavaScript の関数がさまざまなコンテキストで実行される点です。Java プログラミングで this キーワードを使う場合には、そのクラスが使われている、(そのクラスの) 現在のインスタンスを指します。一方 JavaScript の関数の中で this を使う場合には、その関数が実行されているコンテキストを指します。関数は、特に指定されない限り、その関数を定義するクロージャーの中で実行されます。

非常に単純な言い方をすれば、クロージャーは中括弧 ({}) で囲まれた任意の JavaScript コードと考えることができます。JavaScript ファイル内部で宣言される関数は this を使うことでそのファイルの main ボディーの中で宣言される任意の変数にアクセスすることができますが、ハッシュの中で宣言される関数はそのハッシュ内で宣言される変数を参照する場合しか this を使うことはできません (ただしその関数が動作するための別のコンテキストが (その関数に) 与えられている場合は除きます)。

クロージャーの中で実行される関数が Dojo 関数への引数として必要になることはよくあるため、そうした関数のコンテキストを設定する方法を理解できると、本来は不必要なはずのデバッグを大量に行う事態を避けることができます。

コンテキストを割り当てるために主に使われる Dojo 関数は dojo.hitch です。皆さんが dojo.hitch を使うことはないかもしれませんが、dojo.hitch が Dojo で最も基本的な関数の 1 つであること、また他の多くの関数が裏でこの関数を呼び出していることを理解することが重要です。

リスト 3 はコンテキストのヒッチ (hitch: 関連付け) がどのような動作で行われるかを示しています (この動作の出力を図 1 に示します)。

  • ある変数がグローバル・コンテキストで定義され (globalContextVariable)、あるハッシュのコンテキストで別の変数が宣言されます (enclosedVariable)。
  • 関数 accessGlobalContext()globalContextVariable に適切にアクセスし、この変数の値を表示します。
  • しかし enclosedFunction() は、この関数のローカル変数 enclosedVariable にしかアクセスすることができません (globalContextVariable の値は「undefined」と表示されることに注意してください)。
  • dojo.hitch を使って enclosedFunction() をグローバル・コンテキストに「ヒッチ」すると、globalContextVariable が表示されるようになります (ただし今度は enclosedVariable が未定義となります。これは enclosedFunction() が実行されているコンテキストで enclosedVariable が宣言されていないためです)。
リスト 3. クロージャーとコンテキスト
var globalContextVariable = "foo";

function accessGlobalContext() {
    // This will successfully output "foo"...
    console.log(this.globalContextVariable);
};

var myHash = {
    enclosedVariable : "bar",
    enclosedFunction : function() {
                           // Display global context variable...
                           console.log(this.globalContextVariable);

                           // Display enclosed context variable...
                           console.log(this.enclosedVariable);
                       }
};

console.log("Calling accessGlobalContext()...");
accessGlobalContext();

console.log("Calling myHash.enclosedFunction()...");
myHash.enclosedFunction();

console.log("Switch the context using dojo.hitch...");
var switchContext = dojo.hitch(this, myHash.enclosedFunction);
switchContext();
図 1. コンテキスト・ヒッチの動作
コンテキスト・ヒッチの動作

クラスの宣言

クラスの宣言に関するヒント

  • myClass は完全に有効な名前ですが、名前を宣言する際には完全修飾クラス名のスタイルを使う習慣をつけた方が適切です (例えば com.ibm.dojo.myClass など)。これは、そのクラスを「./com/ibm/dojo/」という相対パスの下にあるファイルシステムにデプロイする必要があるという意味ではなく、単に他のクラスとの名前の衝突が起きる可能性を低くするためです。
  • 最後の属性の後に , (カンマ) を付けてはいけません。これは一部のブラウザー (FireFox など) ではこのカンマを無視しますが、他のブラウザー (Internet Explorer など) ではカンマによって動作がおかしくなるためです。このルールはすべての場所におけるハッシュ・オブジェクトの宣言にも当てはまります。

このヒッチがなぜ重要かという理由は、Dojo クラスを宣言したり独自のウィジェットを作成したりし始めると、すぐに明らかになります。Dojo の最大の強みの 1 つは、dojo.connect 関数と組み込みの pub/sub モデルを使ってオブジェクト同士を「結び付け」られることです。

クラスを宣言するためには次の 3 つのオブジェクトが必要です。

  1. そのクラス固有の名前
  2. 関数を継承する元となる親クラス (そして多重継承をシミュレートする「混合」クラス)
  3. すべての属性と関数を定義するハッシュ

宣言できるクラスとして最も単純なものをリスト 4 に、このクラスをインスタンス化する方法をリスト 5 に示します。

リスト 4. 基本的なクラス宣言
dojo.declare(
   "myClass",
   null,
   {}
);
リスト 5. クラスのインスタンス化の基本
var myClassInstance = new myClass();

「本物の」(つまり使い道のある) Dojo クラスを宣言したい場合には、コンストラクターを理解することが重要です。Java コードでは、オーバーロードされたコンストラクターを複数宣言することによって、さまざまに異なるシグニチャーでクラスをインスタンス化することができます。Dojo のクラスでは preambleconstructor、そして postscript を宣言することができますが、大部分の場合はコンストラクターを宣言すればよいだけです。

  • 多重継承をシミュレートするために他のクラスと混合する場合を除き、preamble が必要になることは稀です (preamble を使うと、継承されるクラスや混合されるクラスに実際に渡す前に constructor 引数を操作することができます)。
  • postscript によって Dojo ウィジェットのライフサイクル・メソッドを駆動することができますが、標準的な Dojo クラスには何もメリットがありません。

これらはどれも必ずしも宣言が必要なものではありませんが、クラスのインスタンスに何らかの値を渡すためには、少なくとも constructor 関数を宣言する必要があります。あるクラスの他の引数が constructor 引数にアクセスする場合には、宣言された属性にそれらの constructor 引数を割り当てる必要があります。リスト 6 に示すクラスは、このクラスの constructor 引数の 1 つだけをクラス属性に割り当て、それから別のメソッドの中で両方の引数を参照しようとしています。

リスト 6. constructor 引数の割り当て
dojo.declare(
    "myClass",
    null,
    {
        arg1 : "",
        constructor : function(arg1, arg2) {
                          this.arg1 = arg1;
                      },
        myMethod : function() {
                       console.log(this.arg1 + "," + this.arg2);
                   }
    }
);

var myClassInstance = new myClass("foo", "bar");
myClassInstance.myMethod();
図 2. constructor 引数を割り当てたことによる出力
constructor 引数を割り当てたことによる出力

複合属性のルール

クラスの属性の初期化は宣言時に行いますが、複合オブジェクト型 (ハッシュや配列など) によって属性が初期化される場合には、その属性は Java クラスでの public な静的変数と似たものになります。これはつまり、あるインスタンスによって属性が更新されると、その変更は他のすべてのインスタンスに反映されるということです。この問題を避けるためには複合属性をコンストラクターの中で初期化する必要がありますが、これはストリングやブール値のように単純な属性の場合には必要ありません。

リスト 7. グローバル・クラスの属性
dojo.declare(
    "myClass",
    null,
    {
        globalComplexArg : { val : "foo" },
        localComplexArg : null,
        constructor : function() {
                          this.localComplexArg = { val:"bar" };                          
                      }
    }
);

// Create instances of myClass A and B...
var A = new myClass();
var B = new myClass();

// Output A's attributes...
console.log("A's global val: " + A.globalComplexArg.val); 
console.log("A's local val: " + A.localComplexArg.val); 

// Update both of A's attributes...
A.globalComplexArg.val = "updatedFoo";
A.localComplexArg.val = "updatedBar";

// Update B's attributes...
console.log("A's global val: " + B.globalComplexArg.val);
console.log("A's local val: " + B.localComplexArg.val);
図 3. クラスの属性
クラスの属性

メソッドのオーバーライド

スーパークラスのメソッドは、同じ名前を持つ属性を宣言することによって継承することができます。JavaScript にはオーバーロードという概念はありませんが、これは JavaScript が想定外の引数を無視し、足りない引数はヌルで置き換えるためです。Java コードではオーバーライドされたメソッドを呼び出すために super のメソッド (つまり super().methodName(arg1, arg1);) を呼び出しますが、Dojo では inherited メソッド (this.inherited(arguments);) を使います。リスト 8 では 2 つのクラスが宣言されており、childparent を継承して親の helloWorld メソッドをオーバーライドしていますが、inherited を呼び出して parent の関数にアクセスしています。

リスト 8. Dojo での superclass メソッドの呼び出し
dojo.declare(
    "parent",
    null,
    {
        helloWorld : function() {
                         console.log("parent says 'hello world'");
                     }
    }
);

dojo.declare(
    "child",
    parent,
    {
        helloWorld : function() {
                         this.inherited(arguments); // Call superclass method...
                         console.log("child says 'hello world'");
                     }
    }
);

var child = new child();
child.helloWorld();
図 4. Dojo で superclass メソッドを呼び出したことによる出力
Dojo で superclass メソッドを呼び出したことによる出力

メソッド・コンテキストの設定

リスト 9 に示す Java クラスはインスタンス化されると、提供されたストリング配列の要素をストリングの ArrayList にコピーします。Dojo でも、リスト 10 のコードによって同じ機能を提供できると考えても妥当なように思えます。(targetArray がグローバルにならないようにコンストラクター関数の中でインスタンス化されていることに注意してください。) しかし残念ながら、これは図 5 に示すようなエラー・メッセージになります。この理由は、dojo.forEach メソッド呼び出しの中で宣言された関数は、その関数自身のボディーを参照するものとして this を定義するクロージャーを作成するからです。

リスト 9. Java コードでのクラス・スコープの変数へのアクセス
import java.util.ArrayList;

public class MyClass
{
    // Declare an ArrayList of Strings...
    private ArrayList<String> targetArray = new ArrayList<String>();

    public MyClass(String[] sourceArray)
    {
        // Copy each element of a String[] into the ArrayList...
        for (String val: sourceArray) 
        {
            this.targetArray.add(val);
        }
    }
}
リスト 10. Dojo でコンテキストが定義されていない場合
dojo.declare(
    "myClass",
    null,
    {
        targetArray: null,
        constructor: function(source) {
                         // Initialise in constructor to avoid making global
                         this.targetArray = []; 

                         // Copy each element from source into target...
                         dojo.forEach(source, 
                                    function(item) {
                                        this.targetArray[this.targetArray.length] = item;
                                    });
                     },
    }
);

// This will cause an error!
var myClass = new myClass(["item1","item2"]);
図 5. Dojo でコンテキストが定義されていない場合の出力
Dojo でコンテキストが定義されていない場合の出力

targetArray はこの関数で囲まれたコンテキストで定義されているわけではありませんが、このコンテキストを Dojo 関数の引数として、このコンテキストが定義されている場所で渡すことができます。これはつまり、this キーワードによって、そのコンテキストで宣言された (関数を含む) 任意のオブジェクトにアクセスできるということです。これを適切に実装したものをリスト 11 に示します (追加のコードは太字で示してあります)。

リスト 11. Dojo での適切なコンテキスト設定
dojo.declare(
    "myClass",
    null,
    {
        targetArray: null,
        constructor: function(source) {
                         // Initialise in constructor to avoid making global
                         this.targetArray = []; 

                         // Copy each element from source into target...
                         dojo.forEach(source, 
                                    function(item) {
                                        this.targetArray[this.targetArray.length] = item;
                                    }, this);
                     },
    }
);

コンテキストは必ずしも Dojo 関数のシグニチャーの中で同じ引数として渡されるわけではありません。

  • dojo.subscribe では、コンテキストは関数宣言の>に渡されます (リスト 12)。
  • dojo.connect では、トリガー・メソッドが定義されるコンテキストとターゲット・メソッドが定義されるコンテキストの両方を提供する必要があります。リスト 13 に示す例では、obj1methodA が定義されるコンテキストであり、obj2methodB が定義されるコンテキストです。obj1methodA を呼び出すと、obj2methodB が呼び出されます。
リスト 12. dojo.subscribe でのコンテキスト設定
dojo.declare(
    "myClass",
    null,
    {
        subscribe : function() {
                        dojo.subscribe("publication",
this, 
                                       function(pub) { 
                                           this.handlePublication(pub);
                                       });
                    },

        handlePublication : function(pub) {
                                console.log("Received: " + pub);
                            }
    }
);
リスト 13. dojo.connect でのコンテキスト設定
dojo.connect(obj1, "methodA", obj2, "methodB");

まとめ

JavaScript よりも構造化された Java コードの環境に慣れた開発者にとって、JavaScript はとても自然とは思えないものです。しかし Dojo を実装し、Dojo のクラス宣言機能を利用すると、クライアント・サイドの開発への移行が非常に容易になります。コンテキストをよく理解し、またコンテキストをいつ、どのように設定するのかをよく理解することによって、Java 開発者は数々の苦労を減らすことができ、また開発用のツールボックスに自信を持って JavaScript を追加できるようになります。

参考文献

  • Dojo ツールキットを使い始めるためのすべての情報とリソースが DojoToolkit.org に用意されています。
  • JavaScript の作成を支援する Eclipse ベースのツールについて学ぶために、「JavaScript Development Toolkit の紹介」(developerWorks、2008年5月) を読んでください。
  • technology bookstore には、この記事や他の技術的な話題に関する本が豊富に取り揃えられています。
  • developerWorks の Ajax resource center には (Dojo を含めた) 他の Ajax 技術に関する情報が豊富に用意されています。
  • Dojo API に関する完全な資料も用意されています。
  • Dojo の素晴らしいコード・サンプルが Dojo campus にいくつか紹介されています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=351666
ArticleTitle=Java 開発者にとっての Dojo の概念
publish-date=10142008