prototype.js でデザインパターン - Prototype
続きまして「コピーしてインスタンスを作る」Prototype パターンです。デザパタ本では6章にあたります。
var Main = Class.create(); Main.prototype = { initialize : function () {}, main : function() { var manager = new Manager(); var upen = new UnderlinePen('~'); var mbox = new MessageBox('*'); var sbox = new MessageBox('/'); manager.register("strong message", upen); manager.register("warning box", mbox); manager.register("slash box", sbox); document.writeln('<pre>'); var p1 = manager.create("strong message"); p1.use("Hello, world."); var p2 = manager.create("warning box"); p2.use("Hello, world."); var p3 = manager.create("slash box"); p3.use("Hello, world."); document.writeln('</pre>'); } }
というクライアントがあって、実行結果は
"Hello, world." ~~~~~~~~~~~~~ ***************** * Hello, world. * ***************** ///////////////// / Hello, world. / /////////////////
となることが期待されますと。
Manager インスタンスに文字列生成のための各種雛形インスタンスをぽこぽこ放り込んで、後から "strong message" とか "warning box" といった文字列リテラルをキーに、登録されたインスタンスのコピーを生成して使ってるところがポイントですね。manager.create() で取り出してるインスタンスはあくまで最初に登録したインスタンスのコピーなので、そいつをいじっても登録した雛形インスタンスには影響を及ぼしません。
さて、その Manager クラスです。
var Manager = Class.create(); Manager.prototype = { initialize : function() { this.showcase = new Object; }, register : function (name, proto) { this.showcase[name] = proto; }, create : function(name) { var p = this.showcase[name]; return p.createClone(); } }
Manager にはインスタンスを登録します。その登録されたインスタンスを保持しておくデータ構造は文字列をキーにしたデータ構造...連想配列が良さそうです。と言うことでデザパタ本の Java な実装では HashMap を使っています。JavaScript では Object を連想配列として使うことができるので、それを利用しています。
この Manager クラスの肝は
create : function(name) { var p = this.showcase[name]; return p.createClone(); }
ここですね。登録されてるインスタンスを連想配列から取り出して返却するのですが、そのときにそのまま取り出して返却するんではなく、インスタンスに実装されてる createClone() を呼び出して返却すると。createClone() の中ではインスタンスのコピーが行われます。
で、その登録するインスタンス、の抽象クラスを用意します。
Abstract.Product = function() {}; Abstract.Product.prototype = { use : function() {}, createClone : function() { var obj = new Object; for (var key in this) obj[key] = this[key]; return obj; }, }
本では Product は interface になってますが、ここでは createClone メソッドをサブクラスごとに実装するのではなくスーパークラスでまとめられそうだったので、抽象クラスにしました。
createClone() は、自分自身のコピーインスタンスを返却するメソッドです。JavaScript でインスタンスをコピーする方法は組み込みで用意されているか調べたのですがぱっと見見つからなかったので自前で実装。ここでは for/in でプロパティをコピーしてます。この方法だとプリミティブ型はちゃんとコピーされますが、参照は辿りません。いわゆる shallow copy (浅いコピー)です。複雑なインスタンスに対して使うなら、深いコピーを実装しないとですね。
この Abstract.Product が雛形インスタンスのクラス、つまりは Prototype です。Concrete Prototype を作っていきましょう。Abstract.Product を継承して use() を埋めます。
var UnderlinePen = Class.create(); UnderlinePen.prototype = (new Abstract.Product).extend({ initialize : function(ulchar) { this.ulchar = ulchar; }, use : function(s) { document.writeln('"' + s + '"'); document.write(' '); for (var i = 0; i < s.length; i++) { document.write(this.ulchar); } document.writeln(''); } }); var MessageBox = Class.create(); MessageBox.prototype = (new Abstract.Product).extend({ initialize : function(decochar) { this.decochar = decochar; }, use : function(s) { for (var i = 0; i < s.length + 4; i++) { document.write(this.decochar); } document.writeln(''); document.writeln(this.decochar + ' ' + s + ' ' + this.decochar); for (var i = 0; i < s.length + 4; i++) { document.write(this.decochar); } document.writeln(''); } });
となりました。
Prototype パターンはいままで使ったことがないなあ。使いどころがありそうでない。というか GoF パターンで使ったことあるのって 23 のうちいくつあるんだろう。
prototype.js でデザインパターン - Builder
7つめは、「複雑なインスタンスを組み立てる」Builder パターンです。
var Main = Class.create(); Main.prototype = { initialize : function () {}, main : function () { var builder; if (arguments[0] == 'plain') builder = new TextBuilder(); else builder = new HTMLBuilder(); director = new Director(builder); director.construct(); document.writeln(builder.getResult()); } }
というクライアントがあり、その出力は引数が plain なら
============================ 『Greeting』 ■朝から昼にかけて ・おはようございます。 ・こんにちは。 ■夜に ・こんばんは ・おやすみなさい ・さようなら ============================
という plain text な感じになり、そうじゃない場合は同じ内容が HTML になります。デザパタ本では HTML 出力はファイルになってアウトプットされてますが、ここではブラウザへの出力で実装します。
この出力は、まず文書のタイトルがある。残りは普通の文字列とか、リストがある。最後にフッタを出力。という構造になってます。なので、
- 文書タイトルを作る
- 普通の文字列を作る
- リストを作る
- フッタを出力する
という処理があって、これを plain text 版とか HTML 版を実装すると。その出力を組み立てるのは結構複雑な感じになりそうなので、Builder パターンを適用してみましょう、ってな話ですね。
plain text な Builder である TextBuilder、HTML な HTMLBuilder をそれぞれ実装していくわけですが、その抽象クラスをまず作ります。
Abstract.Builder = function() {}; Abstract.Builder.prototype = { makeTitle : function(title) {}, makeString : function(str) {}, makeItems : function(items) {}, close : function() {}, getResult : function() {} }
て、これは実体のないメソッドのみなクラスなのでいわゆるインタフェースクラス。これまでの例では Java の interface に相当するものは省略してきましたが、Buiilder パターンはこれがないとパターンとして意味を成さないので、(サブクラスにメソッドのオーバーライドを強制させらんないので実質的には意味がないけども) 敢えて用意します。このクラスに用意されてるそれぞれのメソッドが、先の4つの処理に相当します。
で、この Builder の Concrete Builder クラス...の前に Builder を使う人を作ります。
var Director = Class.create(); Director.prototype = { initialize : function(builder) { this.builder = builder; }, construct : function() { this.builder.makeTitle('Greeting'); this.builder.makeString('朝から昼にかけて'); this.builder.makeItems(new Array( 'おはようございます。', 'こんにちは。' )); this.builder.makeString('夜に'); this.builder.makeItems(new Array( 'こんばんは', 'おやすみなさい', 'さようなら' )); this.builder.close(); } }
コンストラクタで Concrete Builder のインスタンスを受け取って、そのインスタンスの各種メソッドを呼び出していきます。Concrete Builder の実装が何であれ、インタフェースは保証されてるので Director はそれを知らなくてもよい、ということです。
このインタフェースが保証されてる、ってとこに Abstract.Builder の存在意義があるわけで、Abstract.Builder がないとその辺がしっくりこなくなっちゃうんですよね。
Concrete Builder はそれぞれ以下の感じです。あ、文字列連結をなんとなく append メソッドでやりたかったので AppendableString なるクラスを作ってみました。(TextBuilder は plain text を出力するんですが、ブラウザで実行結果を確かめるために便宜上 pre タグを出力させてます。)
var TextBuilder = Class.create(); TextBuilder.prototype = (new Abstract.Builder).extend({ initialize : function() { this.buffer = new AppendableString(''); }, makeTitle : function (title) { this.buffer.append('<pre>\n'); this.buffer.append("============================\n"); this.buffer.append("『" + title + "』\n"); this.buffer.append('\n'); }, makeString : function (str) { this.buffer.append('■' + str + '\n'); this.buffer.append('\n'); }, makeItems : function (items) { for (var i = 0; i < items.length; i++) { this.buffer.append(" ・" + items[i] + '\n'); } this.buffer.append('\n'); }, close : function() { this.buffer.append("============================\n"); this.buffer.append('</pre>\n'); }, getResult : function () { return this.buffer.toString(); } }); var HTMLBuilder = Class.create(); HTMLBuilder.prototype = (new Abstract.Builder).extend({ initialize : function () { this.buffer = new AppendableString(''); }, makeTitle : function(title) { this.buffer.append('<html><head><title>' + title + '</title></head></body>'); this.buffer.append('<h1>' + title + '</h1>'); }, makeString : function(str) { this.buffer.append('<p>' + str + '</p>'); }, makeItems : function(items) { this.buffer.append('<ul>'); for (var i = 0; i < items.length; i++) { this.buffer.append('<li>' + items[i] + '</li>'); } this.buffer.append('</ul>'); }, close : function() { this.buffer.append('</body></html>'); }, getResult : function() { return this.buffer.toString(); }, }); var AppendableString = Class.create(); AppendableString.prototype = { initialize : function (str) { this.str = str; }, append : function (str) { this.str = this.str + str; }, toString : function () { return this.str; } }
Builder パターンでは Director が Bulider をコントロールするので、メソッド呼び出しのフローは Director の中に実装されます。Template Method とよく似てますが、Template Method はフローを作るのはスーパークラスでやって、サブクラスで各メソッドの中身を実装する、というところが異なってます。
Director は Builder のメソッド呼び出しのフローを規定するわけですが、Director を使う側からみると、Director が「複雑なメソッド呼び出しをまとめて抽象化してシンプルな API (constructメソッド) を提供してくれてる」ということでこれは Facade パターンでもあります。
...って本の中で同じことが書いてた。
prototype.js でデザインパターン - Iterator
Ruby on Rails や Catalyst のプラグインなんかでは prototype.js という JavaScript のライブラリを使って、Ajax サポートを実現しています。prototype.js とフレームワークが必要な Ajax の JavaScript コードを吐き出してくれるので、Ruby プログラマや Perl プログラマは JavaScript の実装を意識しなくても Ajax なインタフェースが作れる、という風になっています。
こんな感じで prototype.js は Ajax な部分に注目が集まっていますが、ほかにも "Class-style OO" なフレームワークも内包してます。
JavaScript はプロトタイプベースのオブジェクト指向言語で、C++ や Java のようなクラスベースのオブジェクト指向言語とはちょっと実装が異なります。プロトタイプベースのオブジェクト指向...は僕も理解がちょっとあいまいなので他のサイトから引用します。
オブジェクトがスロット(クラスのインスタンスならインスタンス変数やメソッドに相当)の追加をクラスに依存せずに自由にできることを前提としたオブジェクト指向。あるいはそうしたオブジェクトを用いたプログラミングや、それをサポートする機構。
つまり、クラスベースな OO では、クラスにインスタンスの性質(プロパティ)や振る舞い(メソッド)が定義されていて、そのクラスを雛形にオブジェクトを生成する、という感じですが、プロトタイプベース OO ではまず先に実体としてのプロトタイプがあって、そこに性質や振る舞いを付け加えていくことでオブジェクトが形成されていく、という感じです。
そんなわけで JavaScript の OO は通常、
/* コンストラクタ */ function Dog (name) { this.name = name; } /* Dog のメソッド */ Dog.prototype.bark = function () { alert(this.name + ': わんわん'); } dog = new Dog('しなもん'); dog.bark();
みたいな感じで実装します。JavaScript では関数がオブジェクトなので、コンストラクタは Dog というラベルの付いた関数として実装され、そのクラスのプロトタイプに bark() メソッドを実装するといった具合。
で、prototype.js に含まれている Class-Style OO な機能を使うと、JavaScript でももう少しクラスベース風なシンタックスで書くことができるという塩梅です。例えば先の犬の例を prototype.js な OO にすると、
var Dog = Class.create(); Dog.prototype = { initialize : function (name) { this.name = name; }, bark : function () { alert(this.name + ': わんわん'); } } dog = new Dog('しなもん'); dog.bark();
となります。prototype.js は Class というクラスを生成するクラス(紛らわしいな)提供するので、まず
var Dog = Class.create();
としてクラスを作って、あとは prototype に色々付けたしていくと。ちなみに
Dog.prototype = { initialize : function (name) { this.name = name; }, bark : function () { alert(this.name + ': わんわん'); } }
の部分のシンタックスはあまり見慣れないのでこれが prototype.js の機能なのかなと一瞬思っちゃうのですが、これは JavaScript 組み込みのリテラルで、
var obj = { x:1, y:2 };
と書くと x が 1、y が 2 なオブジェクトに obj という名前がつくというものです。つまり、
var obj = new Object; obj.x = 1; obj.y = 2;
と同じということ。Class を使うとインスタンス生成時に initialize メソッドが呼ばれるようになったり、prototype.js に含まれる Object.extend と併用することで
var Dog = Class.create(); Dog.prototype = (new Animal).extend({ .... })
というシンタックスで継承が実現できたりするようになります。
前置きが長くなりました。この prototype.js を使ってデザインパターンなコードを書いてみるというのが今回の趣旨です。題材はもちろん結城さんのデザパタ本。学習がてらいくつか実装してみたので暇をみて掲載していこうかなと。(本当は、実際のウェブアプリケーションを題材にデザインパターンを適用するコードとかの方が良さそうなんですが、いかんせん経験が乏しいものでどういう場合にクライアントサイド JavaScript でデザインパターンを使うことがあるかがまだまったく掴めてなかったり。)
まずは Iterator パターンです。
デザインパターンのコードはまずそのクラス群を利用するコードから見ていくのがわかりやすいです。Iterator パターンでは
var Main = Class.create(); Main.prototype = { initialize : function () {}, main : function () { var shelf = new BookShelf(); shelf.appendBook(new Book('実践ハイパフォーマンス MySQL')); shelf.appendBook(new Book('Perlクックブック')); shelf.appendBook(new Book('Blog Hacks')); var it = shelf.iterator(); while (it.hasNext()) { var book = it.next(); document.writeln(book.getName() + '<br>'); } } }
というコードがあって、
var client = new Main; client.main();
と実行すると、
実践ハイパフォーマンス MySQL Perlクックブック Blog Hacks
と表示されるようなプログラムです。肝は
var it = shelf.iterator(); while (it.hasNext()) { var book = it.next(); document.writeln(book.getName() + '<br>'); }
ですね。shelf という物の集合体があってそこからイテレータを取り出してそいつを回すとその物を辿っていける。物の集合体の実装がどんなデータ構造であれ、同じインタフェースで中のものをひとつずつ取り出していくことができる、と。
それで、デザパタ本のコードでは Java の interface をまず用意して、となるのですが例によって JavaScript は動的な言語かつ型がないのでサブクラスに実装を強制させるメカニズムがない。ということで、ここでは interface に相当するものは用意せずに Concrete クラスだけで実装します。
登場するクラスは、
- Book
- BookShelf
- BookShelfIterator
の 3 つになります。
まずは集合体の中の個々の物である Book クラス。
var Book = Class.create(); Book.prototype = { initialize : function(name) { this.name = name; }, getName : function() { return this.name; } }
本のコードにならって getName() を用意してますが、JavaScript 的には book.name でアクセスしちゃってもいいかもですね。
次、本の入れ物であり集合体のクラスであるところの BookShelf。デザパタ本では Concrete Aggregate に相当するやつですね。
var BookShelf = Class.create(); BookShelf.prototype = { initialize : function() { this.last = 0; this.books = new Array(); }, getBookAt : function(index) { return this.books[index]; }, appendBook : function(book) { this.books[this.last] = book; this.last++; }, getLength : function() { return this.last; }, iterator : function() { return new BookShelfIterator(this); } }
Java みたいにクラスの中では this を省略できるといいんだけど、それは無理っぽいのでこんな塩梅になりました。iterator メソッドでイテレーターを返します。
var BookShelfIterator = Class.create(); BookShelfIterator.prototype = { initialize : function(bookshelf) { this.bookshelf = bookshelf; this.index = 0; }, hasNext : function () { return this.index < this.bookshelf.getLength(); }, next : function() { return this.bookshelf.getBookAt(this.index++); } }
んでもってそのイテレータ。特に難しいところもなし。
僕はこのクラスのコードを、Main も含めて iterator.js という中に全部書いてやって
<script type="text/javascript" src="../prototype.js"></script> <script type="text/javascript" src="iterator.js"></script> <script type="text/javascript"> var main = new Main(); main.main(); </script>
と記述した HTML を用意し実行してます。
ちと長くなりましたがこんな感じで一つ一つのパターンのコードを見せプログラミングしていこうかなと。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
prototype.js でデザインパターン - Adapter
Iterator に続きまして、「一皮かぶせて再利用」な Adapter パターンです。クライアントは
var Main = Class.create(); Main.prototype = { initialize : function() {}, main : function () { var p = new PrintBanner("Hello, World"); p.printWeak(); document.writeln('<br>'); p.printStrong(); } }
といった感じ。PrintBanner クラスは別のレガシーなクラスのアダプタで、インタフェースを肩代わりしてやっていると。
実行結果は
(Hello, World) *Hello, World*
となります。
今回もやっぱり interface に相当するものは作らずにやってみます。(Adapter パターンで interface がないとなんかちょっとむなしい。)
まずはレガシーなクラス (Adaptee)。クライアントはこいつのインタフェースじゃなくてもっと別のインタフェースを欲しているという状況。
var Banner = Class.create(); Banner.prototype = { initialize : function(string) { this.string = string; }, showWithParen : function() { document.writeln("(" + this.string + ")"); }, showWithAster : function() { document.writeln("*" + this.string + "*"); } }
んでもってこのレガシーなインタフェースを新しいインタフェースとして生まれ変わらせてくれるアダプタ君 (Adapter) は以下。
var PrintBanner = Class.create(); PrintBanner.prototype = (new Banner).extend({ initialize : function(string) { Banner.prototype.initialize.apply(this, arguments); }, printWeak : function() { this.showWithParen(); }, printStrong : function() { this.showWithAster(); } });
Adapter パターンでは一枚かぶせるために継承を使う方法と委譲を使う方法といろいろありますが、ここではデザパタ本に習って継承を使う方法で実装してみました。prototype.js の Object.extend を使って Banner を継承してます。
initialize : function(string) { Banner.prototype.initialize.apply(this, arguments); },
と、initialize の中で見慣れないことをやってますが、これはスーパークラスの同一のメソッドを呼ぶ、ということで要は Java で言うところの super()、Perl でいうところの $self->SUPER::new(@_) をやってるといったところ。最初引数の渡し方がわからなくて少し悩んだけど arguments を渡せばいいと prototype.js の中を見てて気付きました。
prototype.js でデザインパターン - Template Method
次は「具体的な処理をサブクラスにまかせる」Template Method パターン。定番ですね。
var Main = Class.create(); Main.prototype = { initialize : function () {}, main : function () { var d1 = new CharDisplay('H'); var d2 = new StringDisplay('Hello, world.'); var d3 = new StringDisplay('Oh! JavaScript'); d1.display(); d2.display(); d3.display(); } }
みたいなクライアントがあります。出力結果として、
[[HHHHH]] +-------------+ |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| +-------------+ +--------------+ |Oh! JavaScript| |Oh! JavaScript| |Oh! JavaScript| |Oh! JavaScript| |Oh! JavaScript| +--------------+
というのを期待してますと。なんとなく処理の呼び出しパターンは決まっていて、その中身の実装だけが違うと言う匂いがしてきます。こんなときは Template Method。
Iterator と Adapter では interface に相当するものは省略してきましたが、今回は抽象クラスをちゃんと用意します。JavaScript での interface みたいなものは Perl とかと一緒で実質意味を持ちませんが、抽象クラスなら実装がスーパークラスにちょこっとあるので意味があります。
Abstract.Display = function() {}; Abstract.Display.prototype = { open : function() {}, print : function() {}, close : function() {}, display : function() { this.open(); for (var i = 0; i < 5; i++) { this.print(); } this.close(); } }
さて、この抽象クラスの書き方ですが
Abstract.Display = function() {}; Abstract.Display.prototype = { ... };
となってていつものとはちょっと違う感じ。これは prototype.js の中で抽象クラスらしきものを作るのにこういうやり方でやってたのでそれをみようみまねしたもの。(Abstract.Foo とすると prototype.js の中の Abstract.Hoge とバッティングするかもしんないのでもしかしたらあんまよくないのかも)
open、print、close とか中身のないメソッドが用意されてますが、これは抽象メソッドな気分です。Perl だと sub foobar { die } とかしますけどね。function () { alert('abstract') } とかしてもいいのかもしれないけど。JavaScript のその辺の慣習はよくわからない。
さて、この抽象クラスを実装する Concrete クラスを作っていきます。CharDisplay と StringDisplay ですね。
CharDisplay = Class.create(); CharDisplay.prototype = (new Abstract.Display).extend({ initialize : function(ch) { this.ch = ch; }, open : function() { document.write("[["); }, print : function() { document.write(this.ch); }, close : function() { document.writeln("]]"); } }); StringDisplay = Class.create(); StringDisplay.prototype = (new Abstract.Display).extend({ initialize : function(string) { this.string = string; this.width = this.string.length; }, open : function () { document.writeln("<pre>"); this.printLine(); }, print : function() { document.writeln("|" + this.string + "|"); }, close : function() { this.printLine(); document.writeln("</pre>"); }, printLine : function() { document.write("+"); for (var i = 0; i < this.width; i++) { document.write("-"); } document.writeln("+"); } });
ひたすら document.write してるだけですので解説は不要でしょう。Java なひとだとかなり this を省略したい衝動に駆られそうですが、そうすると実行できません。
テンプレートを実装するクラスを増やしていけばいろんな出力が作れますよ、と。
prototype.js でデザインパターン - Factory Method
お次は「インスタンス作成をサブクラスにまかせる」Factory Method パターン。これまた定番。
var Main = Class.create(); Main.prototype = { initialize : function() {}, main : function() { var factory = new IDCardFactory(); var cards = new Array( factory.create('Erich Gamma'), factory.create('Richard Helm'), factory.create('Ralph Johnson'), factory.create('John lissides') ); for (var i = 0; i < cards.length; i++) { cards[i].use(); } } }
というクライアントがあります。
クライアントで利用する主人公であるところのインスタンスの生成ロジックを Factory の中に抽象化してやって、具体的な new によるインスタンス生成の束縛からクラスを解放してやりましょう、ということになります。デザパタ本では Factory の中でインスタンスを生成、そのインスタンスを Factory に登録、といった感じでインスタンス生成に関連した別の処理を Factory の中に一緒に抽象化するってことをやってます。
さて、まずは Factory の抽象クラスです。Template Method の時に同じく Abstract.Factory とします。
Abstract.Factory = function() {}; Abstract.Factory.prototype = { create : function (owner) { var p = this.createProduct(owner); this.registerProduct(p); return p; }, createProduct : function (owner) {}, registerProduct : function(product) {}, }
create の中身が Template Method になってますね。これがインスタンス生成に関する処理を一緒に抽象化する、ということなんでしょう。
Concrete Factory を作るまえに、実際に生成される具体的なインスタンスであるところの IDCard を先に作ってしまいましょう。
var IDCard = Class.create(); IDCard.prototype = { initialize : function(owner) { document.writeln(owner + 'のカードを作ります。<br>'); this.owner = owner; }, use : function() { document.writeln(this.owner + 'のカードを使います。<br>'); } }
クライアントでこの IDCard という記述が一切でてこない、というところが Factory Method の肝になります。
お待ちかね、Concrete Factory は以下です。
var IDCardFactory = Class.create(); IDCardFactory.prototype = (new Abstract.Factory).extend({ initialize : function() { this.owners = new Array(); }, createProduct : function(owner) { return new IDCard(owner); }, registerProduct : function(product) { this.owners.push(product); }, });
デザパタ本では Java の ArrayList でデータ構造を持ってますが、JavaScript の Array は ArrayList に同じく可変長なので、それで代用。
実行結果は、
Erich Gammaのカードを作ります。 Richard Helmのカードを作ります。 Ralph Johnsonのカードを作ります。 John lissidesのカードを作ります。 Erich Gammaのカードを使います。 Richard Helmのカードを使います。 Ralph Johnsonのカードを使います。 John lissidesのカードを使います
となります。
prototype.js でデザインパターン - Singleton
次は「たった1つのインスタンス」Singleton パターンです。あるクラスがあって、そのクラスのインスタンスは実行アプリケーションのライフサイクルを通じて唯一に制限したい、何回生成しても同じインスタンスである、というものです。
var Main = Class.create(); Main.prototype = { initialize : function() {}, main : function() { document.writeln('Start.<br>'); var obj1 = Singleton.getInstance(); var obj2 = Singleton.getInstance(); if (obj1 == obj2) { document.writeln('obj1 と obj2 は同じインスタンスです。<br>'); } else { document.writeln('obj1 と obj2 は同じインスタンスではありません。<br>'); } document.writeln('End.'); } }
というクライアントがありますが、Singleton.getInstance() で取得した Singleton クラスのインスタンスは同一であるはずなので、実行結果としては
インスタンスを生成しました Start. obj1 と obj2 は同じインスタンスです。 End.
となる、ということになります。
さて、Singleton パターンの実装ですが JavaScript で実装すると
var Singleton = Class.create(); Singleton.prototype = { initialize : function() { document.writeln('インスタンスを生成しました<br>'); } } Singleton.singleton = new Singleton(); Singleton.getInstance = function() { return Singleton.singleton; }
となるでしょうか。ざっとみてみたところ prototype.js ではクラスメソッドの定義方法は特に特別なシンタックスはなく、prototype.js を使わない場合と同じくで
Singleton.getInstance = function() { return Singleton.singleton; }
と、クラスのプロパティに直接関数を定義してやる、ということになりそうです。
とまあ、これでクライアントが期待しているところと同じ動作にはなるのですが、ちょっと微妙ですね。Java だとコンストラクタを private にすることで new によるインスタンス生成を禁止することができるし、Perl ではコンストラクタを自前で実装するので new の中でインスタンスが一つになるようなロジックを組み立てて、インスタンスが唯一であることを保証できます。
このコードでは new Singleton とされると Singleton インスタンスが新たに生成されてしまうので、インスタンスを一つに縛ることができません。インスタンス生成には getInstance を呼ぶようにという性善説に基づくしかないと。
うまい具合に工夫してやってインスタンス生成を制限できないものかとちょっと試行錯誤してみましたが、いまのところいい方法は思いついていません。指向性メモ::2005-02-24::JavaScriptでデザインパターンその2でも同じことが書かれているなあ。(と、思ったらコメントにそれらしきものが。)