クラスとインスタンスがわかるならクロージャは怖くない
クロージャは怖い?
クロージャがわからない、とにかく意味不明という声をよく聞くような気がします。特に、クラスベースのオブジェクト指向型言語(Java、C++等)を長く扱っていた人にとっては全く新しい概念に感じられてしまうようです。
しかし、そんなことはないはずです。クラスとインスタンスを理解しているなら、インスタンス変数やカプセル化、ゲッター/セッターはごく当たり前の話でしょう。しかしクラスベースではないオブジェクト指向型言語はクラスが定義できないため、それらの機能を実現するのには手間がかかります。クロージャではオブジェクト指向型言語ではほぼ必須と言えるこれらの機能を一部、サポートしています。
つまりクラスベースではごく当たり前の機能が、クラスベースでない言語では定義できないので別の方法を取っており、それに名前を付けてありがたく使っているだけ(ちょっと誇張してますが)ということなんですね。
正確に言うとクロージャのほうがはるか昔からある考え方だと思います。多分。
本当に怖くないの?
クラスとインスタンスがわかっているなら、本当に怖くありません。せっかくなのでクラスベースオブジェクト指向型の言語としてJavaを、クラスベースでないオブジェクト指向型言語としてJavaScriptを、それぞれ例を挙げながら整理してみます。
クラスベースのクラス定義の例
Javaのクラス定義はこのようになります。
class Human{
private String name;
private int age;
// コンストラクタ
public Human(String name, int age){
this.name = name;
this.age = age;
}
// とりあえずageのgetterだけ書いてみます
public ing getAge(){
return age;
}
}
このコードの場合、クラスHumanのインスタンスはname, ageのフィールドを持ちます。フィールドは隠蔽されており、getter/setterでないとアクセスできません。基本中の基本ですね。
クロージャだとどうなる?
JavaScriptで書いてみます。敢えて、命名をJava風にしています。
let Human = (newName, newAge) => {
var name = newName;
var age = newAge;
return () => {
return age;
}
}
このHuman関数を利用するプログラムはこのようになります。
var obj = Human("hogehogekun", 20);
obj(); // 20が取得できる
このように、クロージャは結果的に変数nameとageを隠蔽しています。また、変数nameとvarは変数objを通してスコープが生き続けているのがわかります。クラスベースですと当たり前な話なんですが、クロージャにするとこうなるんですね。
クロージャだとgetterしか作れてなくない?
複数の関数を返しても構いません。ObjectにすればOKです。
let Human = (newName, newAge) => {
var name = newName;
var age = newAge;
return {
getAge : () => {
return age;
},
setAge : (newAge) => {
age = newAge;
}
};
}
利用するときはこのようになります。
var obj = Human("hogehogekun", 20);
obj.getAge(); //20が取得できる
obj.setAge(50); //50を設定できる
obj.getAge(); //50に変わっている
JavaScriptでもクラス書けるけど?
ES2015からはJavaScriptにもクラスが書けるようになりました。しかしカプセル化のための情報隠蔽(privateに相当するキーワード)はまだ実装されていないようで、情報隠蔽を実現したいならばクロージャのほうが適していそうな現状です。
Discussion
import や、requireのようなモジュールパターンと呼ばれる、コードの書き方をしていれば
もうクロージャーは必要ないんじゃないのかなと思っています。
scriptタグとかで複数ファイル指定しても
結局全部1ファイルにまとまってしまう時代にはすごく有用なJavaScriptのテクニックでしたが、今はクロージャー使う場面を思いつきません。