Edited at

Javaプログラマから見たJavaScriptデザインパターン(アクセス制御編 其の壱)

More than 3 years have passed since last update.

本シリーズは、構想段階ではデザインパターンを淡々と列挙していく記事にしようと思っていたが、なかなかまとまらないので、備忘録ついでに今考えていることをそのまま投稿してしまうことにした。

(訳:いきあたりばったりで体系的ではありません。)

  ## 出来ればそのうち整理しよう…。

※ JSの知識には自信ないので間違った点に気付いた方がいらしたらコメント等でご指摘いただけると助かります。

※ 本稿は直接的な「デザインパターン」についてはあまり焦点があたっていないのでご了承ください。


0.はじめに


本編案内

Moduleパターンを利用したアクセス制御について紹介する。

本編を通して、JSにおけるアクセス制御の一例を見てみる。


シリーズ案内


1.Packageクラス

 とりあえず今回のテーマとなるソースコードを提示。

 このような疑似クラスを作ってみた。

 (以下は参考用のコードであり、実用するならばいくつか改造すべきである。)


Package.js


var Package = (function() {

var public = function Package(name) {
var protected = {
state: 0
};

this.defineClass = function defineClass(classDef) {

var private = {};

this[classDef.name] = createFunc(classDef.constructor);

var funcDef;
for (var key in classDef.prototype) {
funcDef = classDef.prototype[key];
if (funcDef instanceof Function) {
this[classDef.name].prototype[key] = createFunc(funcDef);
}
}

function createFunc(proto) {
return function Interceptor() {
setAccessor(this, protected, private);
proto.apply(this, arguments);
clearAccessor(this, protected, private);
};
}
};
};

function setAccessor(instance, protected, private) {
instance.protected = protected;
instance.private = private;
}

function clearAccessor(instance, protected, private) {
protected = instance.protected;
private = instance.private;
delete instance.protected;
delete instance.private;
}

return public;
})();



検証コード


var test = new Package();
test.defineClass({

name: "Class1",

constructor: function Class1() {
this.private.state = 0;
},

prototype: {
input : function input(num) {
this.protected.state += num;
this.private.state += num;
},

output : function output() {
alert([this.protected.state, this.private.state]);
}
}
});

test.defineClass({

name: "Class2",

constructor: function Class2() {
this.private.state = 0;
},

prototype: {
input : function input(num) {
this.protected.state += num;
this.private.state += num;
},

output : function output() {
alert([this.protected.state, this.private.state]);
}
}
});

function assert (arg) {
if (!arg) throw new Error("error of assertion: " + arg);
}

var ins1 = new test.Class1();
var ins2 = new test.Class2();
assert(ins1 instanceof test.Class1);
assert(ins2 instanceof test.Class2);
ins1.input(1);
ins1.output(); // 1, 1
ins2.input(1);
ins2.output(); // 2, 1
ins1.output(); // 2, 1
assert(ins1.protected === undefined);
assert(ins1.private === undefined);
assert(ins2.protected === undefined);
assert(ins2.private === undefined);



2.背景

 僕がJSで詰まったのは、このアクセス制御(継承含む)と例外処理である。

 従って、javaからjsに入った人ならばここで詰まっている方も少なからずいるはずなので、これをテーマにすることとした。


本編の執筆に至る動機

 Moduleパターンによってpublic, parivateの使い分けができるようになったのはいいが、

 個人的には(javaから入ったためか)protectedやprivate、package privateがないと何か寂しい気がする。


パッケージに持たせたい機能

 javaライクに近づけるイメージで、以下のように定義した。

  ・疑似クラスを定義する機能

    クラス定義については、色々落とし穴があるし、カプセル化するのがベターかと思った。

  ・疑似クラスから、アクセス制限されたスコープを参照する機能

    これが本命の機能となる。

    この機能があることで、あとから疑似クラスを定義したときに、実際のスコープが各メソッド

    あまり美しくないが、外に思いつかなかったので、

    コンストラクタをinterceptしてスコープ(レキシカル変数)のライフサイクルを制御する方法で設計した。


3.関連知識

 本クラスを実装する際に参考にしたパターン及びイディオム。


Moduleパターン

 説明済みのため省略。(Link: 導入編


Interceptorパターン

 AOPとして有名なパターン。メソッドの実行前と実行後に処理(横断的関心事)を後から挟むことができる。

 トレースログ出力や例外処理の実装に役立つ。


js

var Test = {

callMethod : function() {
alert("hello.");
}
};
var target = Test.callMethod;
Test.callMethod = function() {
alert("before");
target.apply(this, arguments);
alert("after");
};
Test.callMethod();

 javaは、多くのフレームワークが実装している。

 フレームワークは大体、AOP Allianceのorg.aopalliance.intercept.MethodInterceptor

を使用している模様。

 自作するには、java.lang.reflect.Proxyなどを使う。サンプルコードは補足が長くなり面倒なので省略する。

補足:InterceptorはProxyとも呼ばれていたりするが、GoFのデザインパターンで言うProxyは必要になったときにオブジェクトを生成する意味が強いので、ここでは語弊を防ぐためProxyとは呼ばない。


引数オブジェクト

 引数を1つのオブジェクトにカプセル化することで、変更に強くする。

 今回はPackage.defineClass()の引数に適用した。


js

var Test = {

callMethod : function (obj) {
alert(obj.arg1 + obj.arg2); // 3
}
};

Test.callMethod({
arg1: 1,
arg2: 2
});



java

public class Test {

class Obj {
public int arg1;
public int arg2;
}
static void callMethod(Obj obj) {
System.out.println(obj.arg1 + obj.arg2); // 3
}
public static void main(String[] args) {
Obj obj = new Obj();
Obj.arg1 = 1;
Obj.arg2 = 2;
Test.callMethod(obj);
}
}


4.おわりに

 シリーズ2回目にして我ながら軸のブレを感じる。

 設計前と実装後のプログラムと似た部分があるなあ。

 次回は本編の其の弐として、今回のテーマをさらに掘り下げて、クラス継承と例外処理の書き方について掲載する予定。

 今回のようにメソッド定義等の自作をする場合は、実用的なコードにするために、

 Object.create(), Object.defineProperty()の仕様を参考にして実装していただきたい。

 (次回、余力があれば詳しく触れるつもり。)