仕事などでJSを書くようになって少々経つが、Java信者で頭が固い僕にとってはどうもJSというのは柔らかすぎてしっくりこない部分が多い。
考え方を整理するにはデザインパターンを知るのが早いと、最近思い立ったので改めて調べてみた。
ということで、Javaは大体分かるし、JSも書くけどそこまで詳しくない人向け(つまり自分主体)にまとめておく。
今のところシリーズ化予定。
※ JSの知識には自信ないので間違った点に気付いた方がいらしたらコメント等でご指摘いただけると助かります。
※ デザインパターンとして挙げているコードは、個人的にアレンジしている場合がありますので、ご了承ください。
0.はじめに
本編案内
内容に入る前に、予備知識をおさらい。要点ではないのでざっくり。
シリーズ案内
JSに存在するクラスは6種類
Number, String, Boolean, Null, Undefined, Object
これらはtypeof演算子で判定できる。(Stringは特殊事情があるが割愛)
instanceof演算子の判定条件はコンストラクタ名
var1 instanceof Clazz === trueを満たすためには、変数var1に、「Clazz」という名前のコンストラクタを代入する。
var var1 = new Clazz();
alert(var1 instanceof Clazz); // true
本稿では、便宜上このinstanceof演算子により判定できるオブジェクトを以後「疑似クラス」と呼称する。
JS vs java(コンストラクタの書き方)
以上から、JSで疑似クラスを表現しようとすると以下のような記述になる。
/**
* Clazzクラスのコンストラクタ
*/
var Clazz = function() {
// プロパティ(javaでいうpublicフィールド)
this.property1 = "";
// コンストラクタメソッドの処理
};
alert(new Clazz() instanceof Clazz); // true
alert(new Clazz() instanceof Object); // true
alert(new Clazz() instanceof Function); // 因みにこれはfalseになる
javaで書くと以下と大体同じ意味
public class Clazz {
// フィールド
public String property1 = "";
/**
* Clazzクラスのコンストラクタ
*/
public Clazz() {
// コンストラクタメソッドの処理
}
public static void main(String[] args) {
System.out.println(new Clazz() instanceof Clazz); // true
System.out.println(new Clazz() instanceof Object); // true
}
}
1.Moduleパターン
目的、メリット
カプセル化。JSでprivateなプロパティ・メソッドを持つために使う。
Closureという言葉があるが、このパターンがClosureか否かについては敢えて言及しないこととする。
書式
// 即時関数内で疑似クラスを定義
var Module = ((function() {
// ------ 内部スコープ ------- (S)
// 内部static変数
var privateVar = "";
// コンストラクタメソッド
var constructor = function(){
// 公開インスタンスプロパティ
this.property1 = "";
// 内部インスタンスプロパティ
var property2 = "";
// 公開インスタンスメソッド
this.setterInstance = function(arg) {
property2 = arg;
};
this.getterInstance = function() {
return property2;
};
};
// 公開staticメソッド
constructor.setterStatic = function(arg) {
privateVar = arg;
};
constructor.getterStatic = function() {
return privateVar;
};
// コンストラクタメソッドを返す
return constructor;
// ------ 内部スコープ ------- (E)
})());
類似するjavaのパターン
ない。他の言語はRuntimeで実装されているはずであり、JSでしか使わないだろう。
(javaにClosureがないという意味ではない。)
関連知識
即時関数:
記法①.var func = (new function(){})();
記法②.var func = ((new function(){})());
関数の定義と実行を同時に行い、定義を変数に残さないための記述。
どちらの書き方でも動作に特に違いはない。
(②の方がよいという記載を見かけたが、一般的に①で書く人が多いようだ。)
尚、無名関数内にreturn文がないと意味がない。return文を書かなかった場合、変数funcにundefinedが代入されるのみ。
検証
Module.setterStatic("test1");
alert(Module.getterStatic()); // test1
var mod1 = new Module();
var mod2 = new Module();
alert(mod1 instanceof Module); // true
alert(mod2 instanceof Module); // true
mod1.setterInstance("testA");
mod2.setterInstance("testB");
alert(mod1.getterInstance()); // testA
alert(mod2.getterInstance()); // testB
mod1.property1 = "aaa";
mod2.property1 = "bbb";
alert(mod1.property1); // aaa
alert(mod2.property1); // bbb
alert(mod1.privateVar); // undefined
alert(mod1.property2); // undefined
alert(mod2.privateVar); // undefined
alert(mod2.property2); // undefined
デメリット
prototypeとの相性が悪い。生成コストがかかるため、カプセル化とのトレードオフとなる。
JSのprototypeは、ほとんどprototypeパターンそのものであるので、想像に難くないだろう。
2.Singletonパターン
目的、メリット
インスタンス個数の制御(大体が限定1個)。他のパターンと併用して使う意味が大きいように感じる。
書式
Moduleパターンを使って記述する。
var Singleton = ((function() {
// インスタンス
var instance;
// コンストラクタ
var constructor = function() {
this.propterty1 = "";
}
// staticメソッドの代わりにgetInstanceメソッドを持つObjectを返す
return {
getInstance : function() {
instance = !instance ? (new constructor()) : instance;
return instance;
}
}
})());
類似するjavaのパターン
SingletonはGoFパターンなので当然javaにもある。
public final class Singleton {
private Singleton() {}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
検証
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
alert(instance1 === instance2); // true
instance1.property1 = "test";
alert(instance2.property1); // "test"
// alert(instance1 instanceof Singleton); // 実行不可
// Singletonは、上の書式ではただのObjectであり疑似クラスではない
// どのように記述してもコンストラクタが外部から見えない以上はinstanceofは使えない
デメリット
上記のように、疑似クラスとして定義する方法がない(instanceofで判定不可)ため、クラスを意識できない。
また、Moduleパターンを使う必要があるため、そのデメリットも内包するが、
Singletonを使うならばprototypeは不要であるケースが多いはずなので、気にしなくてもよいのではなかろうか。
3.FactoryMethodパターン
目的、メリット
代表的な意図は以下だと思っている。
・コンストラクタの呼び出し箇所を一元化する
生成に関する前提条件(制約)を設計から与えることができる。
呼び元が集約されるため影響範囲の特定にも役立つ。
・Bridgeパターンの実現
機能側(インターフェース)と実体のインスタンスを疎結合することで、
多態性を保ちながら実体クラスを容易に差し替えることができる。
書式
機能的なインターフェースを定義
var Product = function(){};
Product.prototype = {
use : function() {
throw new Error("abstract method");
}
};
実体を作成するファクトリを定義
var CarFactory = {
createCar1 : function() {
var car = new Product();
car.use = function() {
alert("drive1.");
};
return car;
},
createCar2 : function() {
var car = new Product();
car.use = function() {
alert("drive2.");
};
return car;
}
};
類似するjavaのパターン
これもGoFパターンなので当然javaにもある。
public interface Product {
void use();
}
public final class CarFactory {
public static final Product createCar1() {
return new Product() {
public void use() {
System.out.println("drive1.");
}
};
}
public static final Product createCar2() {
return new Product() {
public void use() {
System.out.println("drive2.");
}
};
}
}
検証
var normalCar = CarFactory.createCar1();
alert(normalCar instanceof Product); // true
normalCar.use(); // "drive1."
var abnormalCar = CarFactory.createCar2();
alert(abnormalCar instanceof Product); // true
abnormalCar.use(); // "drive2."
デメリット
動的型付言語において、本パターンというものは、設計者の思想が伝わりにくく、
相応のドキュメントや実装者のスキルがないと期待するような効果はあげられないかもしれない。