blades.jsの大きな特徴のひとつが、クラスのカプセル化、つまりメンバの隠匿を(限定的にではあるが)実現している点です。
具体的に書くと、
- プロパティ(インスタンスプロパティ)は全てprotectedになります。
- メソッド(インスタンスメソッド)作成時にはそのアクセス指定(public, protected, private)を選ぶことができます。
今回は、そのメカニズムを解説していきます。
JavaScriptではクラスのメンバがすべてpublicになってしまう
まず、以下のような簡単なクラスを考えてみます。
var SomeClass = function(){ this.someProperty = "hoge"; }; SomeClass.prototype.someMethod = function(){ alert("foo"); }; var instance = new SomeClass(); alert(instance.someProperty);//hogeが表示される instance.someMethod();//fooが表示される
上のコードではSomeMethodというメソッドとSomePropertyというプロパティ持ったSomeClassというクラスを作成し、
その後、インスタンスを作成してそのプロパティとメソッドにアクセスしています。
このように、JavaScriptにおいて一般的な方法でクラスを作成した際、そのメンバは事実上publicとなり外から丸見えになってしまいます。
それでは、JavaScriptにおいてクラスのメンバを隠匿する、すなわちクラスのカプセル化を実現する方法はないのでしょうか?
まずはすべてを隠す
まず、上で作成したクラスを少し拡張してみましょう。
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; //インスタンスを作成 var instance = new SomeClass(); //プロパティへのアクセス alert( instance.privateProperty ); alert( instance.publicProperty ); //メソッドへのアクセス instance.privateMethod(); instance.publicMethod();
先ほど作成したSomeClassクラスに新たに以下のようなプロパティとメソッドを作成しました。
- private(にしたい)プロパティ:privateProperty
- public(のままにしたい)プロパティ:publicProperty
- private(にしたい)メソッド:privateMethod
- public(のままにしたい)メソッド:publicMethod
これからこのクラスを元に、privateなメンバを隠し、publicなメンバにはアクセスできるよう改造していこうと思います。
とりあえず、privateもpublicも関係なく、メンバ、さらにはインスタンスまで全てを隠してしまいましょう。
JavaScriptで何かを隠すとなると、function(){}の出番ですね。
インスタンスをfunction(){}で覆ってしまいます。
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; //インスタンスを隠す var instance = new function(){ var instance = new SomeClass(); }; //プロパティへのアクセス alert( instance.privateProperty );//undefined alert( instance.publicProperty );//undefined //メソッドへのアクセス instance.privateMethod();//エラー instance.publicMethod();//エラー
こうすると、全てのメンバにアクセスできなくなります。
(*注 function(){}で覆ったものを(無駄に)newしている理由は後述します。)
ここから、publicなメンバへのアクセス手段を作成していきます。
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; var instance = new function(){ var instance = new SomeClass(); return { publicProperty : instance.publicProperty, publicMethod : function(){ return instance.publicMethod.apply(instance, arguments); } }; }; //インスタンスを隠す alert( instance.privateProperty );//undefined alert( instance.publicProperty );//"public"と表示 //メソッドへのアクセス instance.privateMethod();//エラー instance.publicMethod();//"public"と表示
さきほど、インスタンスとメンバを隠すためにfunction(){}で覆いましたが、その関数がpublicメンバへの参照の一覧を返すように修正しました。
これでpublicなメンバにだけアクセスする事が出来ますね。
さて、これで完成!!!と言いたいところですが、ちょっと注意が必要です。
それはpublicPropertyについてです。
例えば、以下のような操作するとどうでしょうか?
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; var instance = new function(){ var instance = new SomeClass(); return { publicProperty : instance.publicProperty, publicMethod : function(){ return instance.publicMethod.apply(instance, arguments); } }; }; instance.publicPropety = "modified!!"; alert(instance.publicPropety);//"modified!!"と表示はされるけれど・・・
このように代入しても実際のインスタンスのプロパティの値は変更されません。
変更されるのはpublicメンバにアクセスできるようにreturnしたオブジェクトのpublicPropertyなのです。
また、外からの代入はもちろん内部での値の変更も反映されませんね。
この問題を解決するには、以下のような方法が考えられます。
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; var instance = new function(){ var instance = new SomeClass(); return { setPublicProperty : function(val){ instance.publicProperty = val; }, getPublicProperty : function(){ return instance.publicProperty; }, publicMethod : function(){ return instance.publicMethod(); } }; }; alert( instance.getPublicProperty() );//"public"と表示される instance.setPublicProperty("modified!!"); alert( instance.getPublicProperty() );//"modified!!"と表示される
これで外部からのpublicPropertyへの変更も、内部からのpublicPropertyへの変更も問題なくなりました。
このようにpublicなプロパティを作成するには設定&取得用のアクセサメソッドを作成しなければなりません。
最後にこれまでの内容を整理します。
var SomeClass = function(){ this.privateProperty = "private"; this.publicProperty = "public"; }; SomeClass.prototype.privateMethod = function(){ alert("private"); }; SomeClass.prototype.publicMethod = function(){ alert("public"); }; var PseudoConstructor = function(){ var instance = new SomeClass(); return { setPublicProperty : function(val){ instance.publicProperty = val; }, getPublicProperty : function(){ return instance.publicProperty; }, publicMethod : function(){ return instance.publicMethod(); } }; }; var instance = new PseudoConstructor(); alert( instance.privateProperty );//undefined alert( instance.publicProperty );//undefined alert( instance.getPrivateProperty() );//エラー alert( instance.getPublicProperty() );//"public"と表示 instance.privateMethod();//エラー instance.publicMethod();//"public"と表示
前回からの変更点はinstanceをラップした関数を一度、PseudoConstructor(疑似コンストラクタ)という名前の変数に代入している点です。
PseudoConstructorは文字通りコンストラクタではないので大文字で始める必要も、newする必要もありません。
しかし、ユーザはそれを知りませんし知る必要もありません。
よってリファレンス等も含め、あえてnewしています。
(ちなみに、new演算子はnewされた関数がオブジェクトを返す場合はそれをそのまま返してくれます。)
blades.jsではこのPseudoConstructorの生成とpublicメンバへのアクセス手段の作成を自動化しており、クラスを作成する際にユーザーはこのような面倒な記述を行う必要はありません。
クラスのカプセル化を実現しつつ、インスタンスは通常の方法のようにnewで生成できる、これがblades.jsの大きなポイントの一つです
最後に
さて、今回はJavaScriptにおけるクラスのカプセル化について考察しました。
次回はクラスの継承方法について検討したいと思います。