JavaScriptでSingletonパターンを実現することは可能でしょうか?
可能であればどのような方法がありますでしょうか。
ご教授お願いします。
---
橋本 学
今までC#で開発していましたが、プロジェクトを移ってASPを使うことに
なりました(.NETではない)。
元々、Javaプログラマなので、VBScriptよりは、
JavaScript(JScript)のほうを使いたくて悪戦苦闘しています。
JScript/ASP特有の問題ではないのでこちらに投稿させて頂くことに
しました。
JavaScript-2.0 を知らないので、以下は JavaScript-1.2 での説明です。
ので、最近は変わっているのかも知れません。
-*-
JavaScript-1.2 では
* クラスから生成されるオブジェクトの数を制限することはできません。
* コンストラクタを private にすることもできません。
* オブジェクトごとの private な変数を定めることができません。
といった仕様が存在します。
つまり、Singleton パタンは実現できません。
全てのメソッドとプロパティを static にしたクラスを作成することはでき
ますので、こちらで代用することになります。
たとえば次のような感じになります。
<script type="text/javascript">
function Hoge()
{
function getA() { return Hoge.prototype.a; }
function setB(_b) { b = _b; }
function getB() { return b; }
Hoge.prototype.a = null;
Hoge.prototype.getA = getA;
var b;
Hoge.prototype.setB = setB;
Hoge.prototype.getB = getB;
}
Hoge();
Hoge.prototype.a = 1;
document.write( Hoge.prototype.getA() );
Hoge.prototype.setB( 2 );
document.write( Hoge.prototype.getB() );
</script>
しかし、この用法には制限事項が山ほどあります。またデバッグが非常にし
にくい言語仕様も存在します。はっきりいって実用になりません。
(1) 上のサンプルで prototype を書き忘れると動作しなくなります。
「Hoge.a = 10;」などと書いてしまうですね。
しかし、そのくせして、prototype を書き忘れても警告がでません。
prototype を書かない場合の動作が別に定められているからです。
(2) 外部からメソッドの上書きを許してしまいます。protect することが
できません。
「Hoge.prototype.getA = 100;」 などと上書きできてしまいます。
JavaScriptでライブラリを提供する際は、プログラマがライブラリの利用の
仕方を完全に理解していることが前提となります。
JavaScript から Java に入った人ならば Java の高機能さと言語仕様の美し
さに感激するでしょうが、Java になれた人が JavaScript を使うと発狂する
でしょう。補助的に使う分には JavaScript はとても便利なものなのですが。
--
赤間俊一 ak...@home.so-net.ne.jp
In message news:bgnicn$s02$1...@nn-os103.ocn.ad.jp
"Abu" <abu....@abusys.biz> wrote ...
> JavaScriptでSingletonパターンを実現することは可能でしょうか?
> 可能であればどのような方法がありますでしょうか。
C++もJavaもデザインパターンもやった事無いのでよくわからないのですが、
なにか面白そうということで、あちこち見て(Yahooで調べたり本見たり)
作ってみました。
# JavaScriptではあかまさんもおっしゃっているように、実現できなさ
# そうな気がするのですが、出来る範囲ということで。
## インスタンスが生成されないわけじゃなく、単に利用しないと
## というだけなので、コンストラクタの書き方によってまずい
## ケースは多々あると思います。
### そのまえに、根本的に勘違いしてるかも・・・
で、最初に書いたのが、
=== singleton.js ===
function Singleton() {
if (!Singleton.prototype._instance) {
Singleton.prototype._constructor();
Singleton.prototype._instance = Singleton;
}
return Singleton.prototype._instance;
}
Singleton.prototype._constructor = function () {
var _registry = new Array();
function Lookup(_name) {
return _registry[_name];
}
Singleton.Register = function (_name, _instance) {
if (!_registry[_name]) {
_registry[_name] = _instance;
return true;
} else {
return false;
}
}
Singleton.Instance = function (_name) {
return Lookup(_name);
}
}
Singleton();
次に、変化球的なのが、
=== singleton.js ===
Singleton = new function () {
var _registry = new Array();
function Lookup(_name) {
return _registry[_name];
}
this.Register = function (_name, _instance) {
if (!_registry[_name]) {
_registry[_name] = _instance;
return true;
} else {
return false;
}
}
this.Instance = function (_name) {
return Lookup(_name);
}
}
以下、テストコード
=== test.html ===
<script src="singleton.js"></script>
<script>
function test() {
function debugln(msg) {
dbg.document.writeln(msg+"<br>");
}
function Apple() {
if (Singleton.Register("Apple", this)) {
var _s = 0;
this.Stock = function (_n) {
_s += _n;
debugln("Appleを"+_n+"個ストックしました。残り"+_s+"個です。");
}
this.Eat = function (_n) {
if (_s >= _n) {
_s -= _n;
debugln("Appleを"+_n+"個食べました。残り"+_s+"個です。");
} else {
debugln("Appleは"+_n+"個確保できません。残り"+_s+"個です。");
}
}
}
return Singleton.Instance("Apple");
}
var tanaka = new Apple();
var wada = new Apple();
tanaka.Stock(5);
tanaka.Eat(3);
wada .Eat(1);
tanaka.Eat(2);
wada .Stock(5);
tanaka.Eat(2);
}
</script>
<body onload="test()">
<iframe name="dbg" style="width:100%; height:100%"></iframe>
</body>
=== 結果 ===
Appleを5個ストックしました。残り5個です。
Appleを3個食べました。残り2個です。
Appleを1個食べました。残り1個です。
Appleは2個確保できません。残り1個です。
Appleを5個ストックしました。残り6個です。
Appleを2個食べました。残り4個です。
# テストコードは以下を参考にしました。
# http://member.nifty.ne.jp/yamazaki/doc/cpp_design_pattern.html#Singleton
# 週末はサーバーの調子が悪くなるような…
In message news:bh5g8c$efk$1...@news00.iij4u.or.jp
"T. Sugita" <nws-...@bp.iij4u.or.jp> wrote ...
> C++もJavaもデザインパターンもやった事無いのでよくわからないのですが、
やっぱり少し勉強しないと、と思って本を読み始めたところなのですが、
サブクラスとインスタンスの関係が、少々わかりません。
prototype ベースの JavaScript では、継承というと
function A() {
this.test1 = function () {
}
this.test2 = function () {
}
}
function B() {
this.test2 = function () {
}
}
B.prototype = new A;
var b1 = new B();
var b2 = new B();
# B.prototype = new A; だと、var b = new B(); の
# b.constructor が、(当たり前ですが) A のコンストラクタに
# なってしまって、少々気持ち悪い気も・・・
か、もしくは
# JavaScript1.3の.callは以下のどちらかのような
# 感じと考えて良いのでしょうか?
Object.prototype.call = function (obj) {
obj.base_ = this;
obj.base_();
delete obj.base_;
}
Object.prototype.call = function (obj) {
var new_obj = new this;
for (var i in new_obj)
obj[i] = new_obj[i];
}
function B() {
A.call(this); // JavaScript1.3
this.test2 = function () {
}
}
var b1 = new B();
var b2 = new B();
だと思いますが、それぞれ意味が違いますよね。
前者だと new B() したときに、A のコンストラクタは呼ばれない
ので、A でローカル変数を利用していると挙動も違ってきます。
必要があれば、B のコンストラクタで
this.base = A; this.base();
とすることになると思うのですが、一般に(今読んでるのは
Javaベース)サブクラスのインスタンス化とは、JavaScriptでは
どのようになるのでしょうか?
もしくは、用語としては独立していて、場合(やりたいこと)に
よって(修飾が)変わるのでしょうか?
言語の特性を理解して、やりたいことに合わせて記述というの
でも良い気もしますが、少々用語で混乱しているところです。
# まだ十数ページなので、読みすすめば書いてるかもですが…(^^;;
4年くらい前に考えた方法ですが、JavaScript-1.2 で無理矢理継承をするに
は次のようにしています。
もう少し詳しいことは web 検索で "ECMAScript" をキーワードにしてくださ
い。
-*-
<script type="text/javascript">
function SuperClass()
{
function SuperClass_myMethod()
{
document.write( "I am SuperClass Method.<BR>" );
}
SuperClass.prototype.myMethod = SuperClass_myMethod;
}
function SubClass()
{
function SuperClass_extends_to_SubClass()
{
function SubClass_anotherMethod()
{
document.write( "I am SubClass Method.<BR>" );
}
this.anotherMethod = SubClass_anotherMethod;
this.constructor = SubClass;
return this;
}
SuperClass.prototype.extends_to_SubClass = SuperClass_extends_to_SubClass;
var superclass = new SuperClass();
var subclass = superclass.extends_to_SubClass();
delete SuperClass.prototype.extends_to_SubClass;
return subclass;
}
var oSuper = new SuperClass();
var oSub = new SubClass();
oSuper.myMethod();
oSub.myMethod();
oSub.anotherMethod();
// oSuper.anotherMethod(); ← エラーになります。
</script>
--
赤間俊一 ak...@home.so-net.ne.jp
ところで、継承ですが、僕は以下のサイトを参照していました。
http://www.tokumaru.org/JavaScript/index.htm
ご参考に。
---
橋本 学
> ところで、継承ですが、僕は以下のサイトを参照していました。
> http://www.tokumaru.org/JavaScript/index.htm
その昔、そのサイトを見たことがあります。
でも、prototype を for in で回してコピーするだけじゃ、うまく行かない
んですよね。this や var を使って定義されている値/メソッドもあるわけで
すし。たとえば、上のサイトの方法では次のコードは継承できないですね。
function TestClass(){
var a = 10;
this.getA = function getA(){ return a; }
return this;
}
JavaScript の言語特性を知った上で、その継承の動作を実現するためには、
JavaScript では結構複雑なことをしないといけないはずです。
-*-
ECMAScript(JavaScript-1.2ベース)はあれこれ不便です。
JavaScript-2.0 に期待することは、
継承
protect
private
あたりがちゃんと実装されてくれることでしょうか。それが ECMAScript とし
て標準化されてくれるのであれば最高なんですが。。。
--
赤間俊一 ak...@home.so-net.ne.jp
In message news:20030819104001...@home.so-net.ne.jp
"AKAMA Shun-ichi" <ak...@home.so-net.ne.jp> wrote ...
> function SuperClass()
> SuperClass.prototype.myMethod = SuperClass_myMethod;
> function SubClass()
> var superclass = new SuperClass();
この形式だと、後で prototype で追加しても対応できるので、
なかなか良さそうです。
ただ、スーパークラスのコンストラクタが prototype に対して
アクセスしていますので、それだと SuperClass で private な
変数を使うと、共有してしまいますよね。
それだと、
function SuperClass() {
this.myMethod = function () {
document.write( "I am SuperClass Method.<BR>" );
}
}
function SubClass() {
this.anotherMethod = function () {
document.write( "I am SubClass Method.<BR>" );
}
this.constructor = SubClass;
}
SubClass.prototype = new SuperClass();
でも(SuperClassのコンストラクタは1回しか呼ばれませんが)
同様の効果は得られるように思います。
ところで、たとえば Java でスーパークラスに private な
変数を置いた場合、サブクラスが new で複数インスタンス化
されると、元にしたスーパークラスの private な空間も複数
になるのでしょうか?
別途なにか指定すると変わるという仕様かなという気もして
ますが、なかなか読み進まなくて・・・(^^;;
"T. Sugita" <nws-...@bp.iij4u.or.jp>さん wrote:
> ただ、スーパークラスのコンストラクタが prototype に対して
> アクセスしていますので、それだと SuperClass で private な
> 変数を使うと、共有してしまいますよね。
JavaScript でオブジェクト指向プログラミングをする場合、オブジェクトに
private な変数は使うべきではないと考えています。次のデモコードを実行す
ると、私がそう主張する理由がわかると思います。
クラスの定義に private な変数を用いると、そのクラスから生成されたオブ
ジェクトの間で、その変数が共有されてしまうのです。あえていうのでしたら
「private でクラスに static」な扱いとなります。
<script type="text/javascript">
function Demo()
{
var p = 1;
function Demo_setVar( _int )
{
p = _int;
}
Demo.prototype.setVar = Demo_setVar;
function Demo_getVar()
{
return p;
}
Demo.prototype.getVar = Demo_getVar;
return this;
}
var demo1 = new Demo();
var demo2 = new Demo();
demo1.setVar( 5 );
document.write( demo1.getVar() +"<BR>");
demo2.setVar( 10 );
document.write( demo2.getVar() +"<BR>");
document.write( demo1.getVar() +"<BR>");
</script>
--
赤間俊一 ak...@home.so-net.ne.jp
# 昨日の朝にこの件で自己フォローしたつもりだったのですが、
# ローカルサーバーのグループに投稿してました。(^^;;;
## で、引き続きこの問題について考えてみました。
In message news:20030820103851...@home.so-net.ne.jp
"AKAMA Shun-ichi" <ak...@home.so-net.ne.jp> wrote ...
> クラスの定義に private な変数を用いると、そのクラスから生成されたオブ
> ジェクトの間で、その変数が共有されてしまうのです。あえていうのでしたら
> 「private でクラスに static」な扱いとなります。
私もいくつかテストしてみてたのですが、以下の場合、非常に
問題があるようです。
<script>
function A() {
var s=0;
A.prototype.test = function (n) { s+=n; return s; }
}
var a1 = new A();
var x1 = a1.test(1);
var a2 = new A();
var a3 = new A();
var x2 = a2.test(2);
var x3 = a3.test(3);
document.writeln(x1+","+x2+","+x3+"<br>");
</script>
結果 : 1,2,5 # a2 の初期値が 0
ただ、ローカルスコープで共有という話だと、ちょっと工夫して
以下のようにしてやると、なにかに利用できそうです。
function A() {
if (!A.prototype.test) {
var s=0;
A.prototype.test = function (n) { s+=n; return s; }
}
}
結果 : 1,3,6
前者で、なぜ初期化されるのかは、当初、同じ参照先を利用
していることから、コンストラクタの呼び出しで再初期化
されたのかな?と勘違いしていたのですが、今は以下のように
考えています。
共有されたように見えるメカニズムですが、new することで
prototype から参照テーブルを生成し、property や method
が override されていないことから、親の prototype が参照
されていて、new によってインスタンス が生成され、その
コンストラクタが呼び出される毎に、参照先 this のメモリ
空間に生成された private な変数や関数領域を参照するよう、
親である prototype の参照先を更新していることから、
結果として、override されていない method の場合は
参照先が同じなため、同じローカル変数をアクセスしている
ということになると思います。
<script>
function A() {
var s=0;
A.prototype.test = function (n) { s+=n; return s; }
this.test2 = function (n) { s+=n; return s; }
}
var a1 = new A();
var a2 = new A();
var a3 = new A();
var x1 = a1.test (1);
var x2 = a2.test2(2);
var x3 = a3.test (3);
var x4 = a2.test2(4);
var x5 = a1.test2(5);
var x6 = A.prototype.test(6);
var x7 = a3.test2(7);
document.writeln(x1+","+x2+","+x3+","+x4+","+x5+","+x6+","+x7+"<br>");
document.writeln((a1 == a3 ) + "<br>");
document.writeln((a1.test == a3.test ) + "<br>");
document.writeln((a1.test2 == a3.test2) + "<br>");
document.writeln((A.prototype.test == a3.test) + "<br>");
</script>
結果 : 1,2,4,6,5,10,17
false
true
false
true
上記結果から、x1,x5 は a1 の各 method の参照先が異なり、
x2,x4 から、同一インスタンスの同一 method は同じ場所を
参照していて、x4,x5 から、異なるインスタンスに属した
同一メソッドは異なる場所を参照していることがわかります。
x1,x3,x6 は異なるインスタンスで prototype に属した
メソッドが同じ領域をアクセスしていることがわかります。
また、x3,x6,x7 から、x1,x3,x6 の prototype に属した
メソッドと最後に更新された異なるインスタンスに属した
メソッドが同じ領域をアクセスしていることがわかります。
上記結果から、ローカル変数は使えるが、prototype を利用
した場合は気をつける必要があり、static として利用可能
である、ということになるかなと思います。
# あれもこれもとテストパターンを付け加えたら、結果が
# わかりづらくなってしまいました。(^^;
前回のポストの主旨は、次の2つでした。やや言葉足らずでした。
1.
this で定義したメソッドならば、そのメソッドの内部での変数のスコープは
ちゃんと生成されたオブジェクトの内部に収まります。問題は prototype で
定義したメソッドの内部だけ、変数のスコープが別になるんですよね。それを
「private でクラスにstatic」と表現しました。
このような言語仕様に沿って考えると、JavaScriptで継承を実装する場合は、
わざと
^^^^^^
"T. Sugita" <nws-...@bp.iij4u.or.jp>さん wrote:
> ただ、スーパークラスのコンストラクタが prototype に対して
> アクセスしていますので、それだと SuperClass で private な
> 変数を使うと、共有してしまいますよね。
(再引用)
と実装しなくては、言語仕様に一貫性がでてこないですよね。
# つまり private な変数は、値を含めて引き継がなくてなくてはならない。
2.
しかしこのように実装すると、継承を重ねた場合に private な変数のスコー
プが混乱します。したがって使うべきではないと。
-*-
初期の JavaScript を使っていたときにこのスコープに苦しみ、Microsoft
が JScript でこの言語を再実装したときは、よくここまで互換性が取れたも
のだ、と感心しました。
--
赤間俊一 ak...@home.so-net.ne.jp
In message news:20030822105846...@home.so-net.ne.jp
"AKAMA Shun-ichi" <ak...@home.so-net.ne.jp> wrote ...
> this で定義したメソッドならば、そのメソッドの内部での変数のスコープは
> ちゃんと生成されたオブジェクトの内部に収まります。問題は prototype で
> 定義したメソッドの内部だけ、変数のスコープが別になるんですよね。それを
> 「private でクラスにstatic」と表現しました。
そういうことだったのですね。了解しました。
ただ、スコープが別といえば別なのですが、メソッドの実体が所属
するインスタンスへアクセスしているという意味では、this も
prototype も共通で、オブジェクトからでなくメソッドからみれば、
スコープは同じという気もしますので、表現が難しいところですね。
> と実装しなくては、言語仕様に一貫性がでてこないですよね。
>
> # つまり private な変数は、値を含めて引き継がなくてなくてはならない。
たしかにそうかもしれません。
このあたりは、継承をもうすこし勉強してから、再度検討してみます。