継承ってなんだろう? (3)
最初に注意
自分はつい最近JavaScriptをかじったばかりの,ただの初級者です.以下の文章は単に自分はこう考えている,というだけで,誤りが含まれている可能性は十分にあります.もし読まれる方がいたら,その点を十分にご理解ください.また誤りの指摘は大歓迎です.(以上、コピペ)
Circleオブジェクト
継承の話を始める前に、まずはサンプルから.これまでずっとPointオブジェクトをサンプルとしていたので,今回Pointオブジェクトを継承するものとして,Circleオブジェクトを考えます.
CircleがPointを継承するって変ですか?関係としてis-aというより,has-aでないかと?まぁ,その話を始めるとキリがなくなるので置いておきましょう.
まぁ,Pointの定義は以下のような感じとして
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.x = void 0; Point.prototype.y = void 0; Point.prototype.toString = function(){ return '(' + this.x + ',' + this.y + ')' ; };
Circleは,こんな感じで使えたらよいですよね.
var circle = new Circle(0, 0, 5); document.write('constructor:='+circle.constructor+"<br />"); document.write('circle:='+circle.toString()+"<br />"); document.write('area:='+circle.getArea()+"<br />"); document.write('circumference:='+circle.getCircumference()+"<br />"); // 実行結果 // ---------------------------------------------------- // constructor:=function Circle() { ..... } // circle:=(0,0), r:=5 // area:=78.5398..... // circumference:=31.41592.....
さて,JavaScriptにはクラスという概念がなく,クラスベースの継承もできません.しかしプロトタイプベースの継承という概念はあります.とりあえずこれでいってみましょうか?
__proto__を使った継承
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.x = void 0; Point.prototype.y = void 0; Point.prototype.toString = function(){ return '(' + this.x + ',' + this.y + ')'; }; function Circle(x, y, r) { this.x = x; this.y = y; this.r = r; } Circle.prototype.r = void 0; Circle.prototype.toString = function(){ return Point.prototype.toString.apply(this) + ', r:=' + this.r; }; Circle.prototype.getArea = function(){ return this.r * this.r * Math.PI; }; Circle.prototype.getCircumference = function(){ return this.r * 2 * Math.PI; }; Circle.prototype.__proto__ = Point.prototype; var circle = new Circle(0,0,5); document.write('constructor:='+circle.constructor+"<br />"); document.write('circle:='+circle.toString()+"<br />"); document.write('area:='+circle.getArea()+"<br />"); document.write('circumference:='+circle.getCircumference()+"<br />"); // 実行結果 // ---------------------------------------------------- // constructor:=function Circle(x, y, r) { this.x = x; this.y = y; this.r = r; } // circle:=(0,0), r:=5 // area:=78.53981633974483 // circumference:=31.41592653589793
なんとなく,うまくいってそうな気がします.しかし問題なのは,このようなコーディングが行なえるのはFireFoxのみだということです.IEでは,__proto__が使えず,代替となるものもありません.というわけで,とても困りました.
__proto__を使わない継承(もどき)
そんなわけで別の手段をいろいろ調べてみたのですが,どうもJavaScriptでは言語仕様的に継承をサポートしていないようです.そこで,多くの人が継承もどきを考えています.
- プロトタイプチェーンを使用しない--プロパティコピー
- プロトタイプチェーンを使用する--継承先プロトタイプを継承元オブジェクトで上書き
継承先プロトタイプを継承元オブジェクトで上書きとは,要はこんなコードです.
Circle.prototype = new Point(void 0, void 0);
Circle.ptorotype.constructor = Circle;
まぁ,なるほどと思えなくはないのですが,どうも奥歯にものが挟まったような違和感を覚えます.
継承なんていらない!
そもそもJavaScriptでは,プロパティを動的に追加・削除できるのでした.そして,いわゆる'クラス'に相当するものはObjectのみで,ユーザ定義型というものは存在せず,型チェックもギリギリまで行なわないのでした.このような特徴を持つ言語に,本当に継承なんて必要なのか?というのは考えたほうがよいと思います.
そしてそう考えていくと,そもそも継承とはなにか?という疑問に突き当たります.たとえば,汎化・特化という概念を実現するためのもの,と考えると,やっぱりそれはクラスがないと具合が悪い気がします.多態もそうです.多態を実現するよくある方法は,継承とアップキャストそして動的結合の組み合わせですが,アップキャストもクラスがないことには説明つきません(というか,すべてがObjectなので,アップもダウンもないって感じかな?).結局継承とか多態といった概念はクラスありきの概念のようにも思います.
自分が最初にオブジェクト指向言語に触れたのはC++でした.それからJava,C#を勉強していった中で,クラスが存在するのが当たり前に感じるようになってました.そして,継承といった概念を説明するときに,クラスとオブジェクトの違いをあまり気にすることがなかったように思います.その点JavaScriptはクラスがない,ある意味真のオブジェクト指向言語だといえるのかもしれません.このような全く考え方の異なる言語に無理やり別のスキームをはめ込むのも,なんだか野暮な気がするのです.
(現実問題としては,委譲ベースで考えていくことになると思います.最近自分自身はあまり継承にこだわらなくなっているので,なくてもそんなに困らないかな?と思っています.)
次回は...
Functionについて考えます.とはいえ,ほとんどが妄想になる予感.関数オブジェクトって,ただのオブジェクトとどこが違うのか?みたいな.