2007/05/03

prototype.jsでプライベートメソッドを持つクラスを作る方法


Private Members in JavaScript
[翻訳]JavaScriptにおけるプライベートメンバ(COLLECTION & COPY)

上記の記事に刺激を受けて、prototype.jsでプライベートメソッドを持つクラスを作る方法を考えてみました。

JavaScriptのクロージャを利用してますが、具体的には次のようになります。 

   1:  //Animalクラス作成
   2:  var Animal = Class.create();
   3:   
   4:  //Animalクラスフィールド設定
   5:  Animal.MALE = 0;
   6:  Animal.FEMALE = 1;
   7:  Animal.SEXTYPE = ['Male','Female'];
   8:   
   9:  //Animalクラス設定
  10:  Animal.prototype = {
  11:    initialize : function(_sex) {
  12:      this.define(_sex);
  13:    },
  14:   
  15:    define : function(_sex) {
  16:      //プライベートフィールド
  17:      var sex = _sex;
  18:   
  19:      //プライベートメソッド
  20:      var _getSex = function() {
  21:        return sex;
  22:      }
  23:   
  24:      //Privileged(特権)メソッド
  25:      this.getSex = function (){
  26:        return Animal.SEXTYPE[_getSex()];
  27:      }
  28:   }
  29:  }

 

ポイントの1つ目は、Animal#defineメソッド内のローカル変数として、sexを定義していることです(17行目)。メソッド内のローカル変数ですので、当然外部からは呼び出せず、プライベートフィールドとして扱うことができます。

次のポイントは、Animal#defineメソッド内で、_getSexというメソッドを記述していることです(20行目)。_getSexメソッドは、プライベートメソッドとして動作します。また、_getSexメソッドでは、Animal#defineメソッド内のローカル変数にアクセスすることができます。不思議な感じですが、この辺がJavaScriptのクロージャという機能になります。

最後のポイントは、Animal#getSexメソッドです(25行目)。Animal#getSexメソッドは、Animal#defineメソッド内の関数ですが、thisオブジェクトに関数を追加しています。そのため、Animal#defineメソッド内のプライベートフィールド・プライベートメソッドにアクセス可能な上に、外部から呼び出し可能なパブリックメソッドになります。この特殊なメソッドを、元記事では「Privileged(特権)メソッド」と呼んでいます。

 

このAnimalクラスを実行すると、次のようになります。

   1:  var animal = new Animal(Animal.MALE);
   2:  //alert(animal.sex);        // undefined
   3:  //alert(animal._getSex());  // Error
   4:  alert(animal.getSex());     // Male

プライベートフィールドにも、プライベートメソッドにもアクセスできず、Privileged(特権)メソッドは問題なく実行できています。

 

これだけではおもしろくないので、継承についても考えてみました。

   1:  //Personクラス生成
   2:  var Person = Class.create();
   3:   
   4:  //Animalクラスフィールドコピー
   5:  copyMembers(Person, Animal);
   6:   
   7:  //Personクラス設定
   8:  Person.prototype = {
   9:    initialize : function(_name, _sex) {
  10:      this.define(_name, _sex);
  11:    },
  12:   
  13:    define : function(_name, _sex) {
  14:    
  15:      //Animalクラスのdefineメソッドを実行
  16:      var func = eval(Animal.prototype.define);
  17:      func.apply(this, [_sex]);
  18:   
  19:      //プライベートフィールド
  20:      var name = _name;
  21:   
  22:      //プライベートメソッド
  23:      var _getName = function(){
  24:        return name;
  25:      }
  26:   
  27:      //Privileged(特権)メソッド
  28:      this.getName = function() {
  29:        return _getName();
  30:      }
  31:    }
  32:  }
  33:   
  34:  //クラスメンバをコピー
  35:  function copyMembers(_target, _from) {
  36:   var exclude_methods 
                 = ['prototype','bind','bindAsEventListener'];
  37:   
  38:   for (var member in _from) {
  39:     var key = member.toString();
  40:     if (exclude_methods.indexOf(key) == -1) {
  41:       _target[key] = _from[key];
  42:     }
  43:   }
  44:  }

 

ポイントの1つ目は、5行目でcopyMembers関数を実行しているところです。

通常のオブジェクト指向では、インスタンスフィールドやインスタンスメソッドだけではなく、クラスフィールドやクラスメソッドも継承します。しかし、prototype.jsでは、prototypeしかコピーしないため、インスタンスフィールドとインスタンスメソッドしか継承できません。

これにはどうも違和感があったので、このcopyMembers関数を使用して、AnimalクラスのクラスフィールドとクラスメソッドをPersonクラスに継承できるようにしています。

次のポイントは、prototype.jsを使っているのに、Object.extendを使用した継承を行っていない点でしょうか。

最後のポイントは、16~17行目です。この2行でAnimal#defineメソッドを実行して、Animalクラスで実装している内容を、Personクラスでも実装します。これは、Animalクラスの実装を、Animal#defineメソッドに集約させているために可能になります。このため、Object.extendを使用しなくても継承を行なうことができています。

 

Personクラスを実行すると、以下のようになります。

   1:  var taro = new Person('Taro',Person.MALE);
   2:  //alert(taro.name);         // undefined
   3:  //alert(taro._getName());   // Error
   4:  alert(taro.getName());      // Taro
   5:  alert(taro.getSex());       // Male
   6:   
   7:  var ellen = new Person('Ellen',Person.FEMALE);
   8:  //alert(ellen.name);        // undefined
   9:  //alert(ellen._getName());  // Error
  10:  alert(ellen.getName());     // Ellen
  11:  alert(ellen.getSex());      // Female

1行目では、Person.MALEを使用できているので、Animalクラスからのクラスフィールドの継承がうまくいっていることが分かります。

また、5行目では、Animalクラスのメソッド実行がうまくいっているので、スーパークラスのメソッド実行も問題なくできています。

JavaScriptでカプセル化はできないと思っていましたが、やりようによっては結構できるものですね。

なお、この記事のコードは、IE7, FireFox2.0, Opera9.01で動作確認を行っています。

 


スポンサーリンク


このエントリーをはてなブックマークに追加




Twitter ではブログにはない、いろんな情報を発信しています。


コメント

コメントを書く



プロフィール

  • 名前:fnya
    経歴:
    SE としての経験は15年以上。様々な言語と環境で業務系システム開発を行い、セキュリティ対策などもしていました。現在は趣味SE。

    Twitter では、ブログでは取り上げない情報も公開しています。


    ブログについて

    このブログは、IT、スマートフォン、タブレット、システム開発などに関するさまざまな話題を取り上げたり、雑感などをつづっています。

    現在、Enty で支援を受け付けています。もしよければご支援ください。



    >>ブログ詳細
    >>自作ツール
    >>運営サイト
    >>Windows 10 まとめ

    Twitter のフォローはこちらから Facebook ページはこちら Google+ページはこちら RSSフィードのご登録はこちらから