Web技術勉強会 20110723

24
Web技術勉強 2011/07/23 Ryuichi TANAKA/@mapserver2007/summer-lights.jp JavaScriptでタイスオジェク指向ググ ~続々親子関係を維持してクスを使わないオジェク指向ググ手法~

description

 

Transcript of Web技術勉強会 20110723

Page 1: Web技術勉強会 20110723

Web技術勉強会2011/07/23

Ryuichi TANAKA/@mapserver2007/summer-lights.jp

JavaScriptでプロトタイプベースオブジェクト指向プログラミング

~続々・親子関係を維持してクラスを使わないオブジェクト指向プログラミング手法~

Page 2: Web技術勉強会 20110723

これまでの内容

� JavaScriptのプロトタイプベースOOPライブラリ「mix.js」を開発を開始

� mix.jsの初期バージョン完成

� バグあり。IEで動かない。

� バグ修正版mix.jsをリリース

� バグなし、IEでも動作。� バグなし、IEでも動作。

� mix.js用ライブラリの開発

� 前回一部紹介。

Page 3: Web技術勉強会 20110723

順調に育ってます

� 開発から約2ヶ月。

� mix.js本体:213行

� コメント、改行のみの行を含んでいるので実質190行くらい。

� mix.modules.js:743行

� モジュール群。実質680行くらい。

� Utils、Cache、Cookie、Http、Design� Utils、Cache、Cookie、Http、Design

� アプリケーションにも現在適用中(RankForce3, Rails製のアプリ)。

Page 4: Web技術勉強会 20110723

ライブラリ開発でレベルアップ

� これまで触ったことのない深い知識が必要

� プロトタイプチェーンのつなぎ替え

� クロージャ連発

� 厳密なエラー処理

� テスト

� このライブラリ開発でかなり詳しくなれた、と自負。� このライブラリ開発でかなり詳しくなれた、と自負。

� JavaScriptに関しては中級者以上の実力があると、勝手に自負してるが、このライブラリは我ながらかなり使えると思ってる。なので、これからどんどん使っていく予定。

Page 5: Web技術勉強会 20110723

ライブラリ開発は大変

� 使う側から、使わせる側へ

� IEで動かすことが特に大変

� Chromeでは動くが、IEで動かないことはざら。だが、IEを簡単に切り捨てることは負けを認めたことになる。

� テストがないと本当に死ねる

� テストをつねに通る状態にしておかないと、機能追加や仕様変更� テストをつねに通る状態にしておかないと、機能追加や仕様変更で地獄を見る。

� 通常のアプリと違って、影響範囲がほぼコード全域に及ぶ。したがって、使い捨てのテストコードで都度確認するわけにはいかない。常に過去に正しく動いていて、かつ、変更後も正しく動くことを保証し続けないといけない。

� テストがなかったら途中で投げてたかも。

� おかげで、機能変更がまったく怖くない。

Page 6: Web技術勉強会 20110723

閑話休題

� ここから本題

Page 7: Web技術勉強会 20110723

今回の内容

� まずはこのコードをみてほしい。

� Child.java

package jp.sample;

public class Child extends Parent {

public void caller() {

super.caller();super.caller();

}

public void name() {

System.out.println("name is child");

}

}

Page 8: Web技術勉強会 20110723

今回の内容� Parent.java

package jp.sample;

public class Parent extends GrandParent {

public void caller() {

super.caller();

}}

public void name() {

System.out.println("name is parent");

}

}

Page 9: Web技術勉強会 20110723

今回の内容� Parent.java

package jp.sample;

public class GrandParent {

public void caller() {

name(); // これがどこを指すか?}}

public void name() {

System.out.println("name is grand parent");

}

}

Page 10: Web技術勉強会 20110723

これを実行すると

� Test.java

package jp.sample;

public class Test {

public static void main(String[] args) {

Child child = new Child();

child.caller();

� 問題:

� これを実行すると、何と表示されるでしょう?

child.caller();

}

}

Page 11: Web技術勉強会 20110723

クラス図だとこんな

手順:・Childをインスタンス化・Child#callerを実行する。・Child#callerはParent#calerを呼び出す・Parent#callerはGrandParent#callerを呼び出す・GrapndParent#callerはGrandParent#nameを

呼び出す。

GrandParent#nameはどこを指す?

Page 12: Web技術勉強会 20110723

答え

� 答え。

� だからどうした、Javaじゃねえか!

� はい。でも、なんで「name is grand parent」じゃないのか。なんとなく不思議に思わないかい?

$ > java Test

$ > name is child

のか。なんとなく不思議に思わないかい?

Page 13: Web技術勉強会 20110723

レシーバを意識してみる

child.super()super().caller()

イメージとしてはこんな感じになる。(あくまでイメージ)

child.super().caller()

レシーバは常にchildになるので、GrandParent#callerが呼ばれてもchildとして呼ばれることになる。したがってname()はChild#name。

ちなみにレシーバはRuby用語。

Page 14: Web技術勉強会 20110723

だが、JavaScriptだとこうはならない

� JavaScriptで同じことをやると…

child.super().super().caller()

parent.super().caller()

grandparent.caller()grandparent.caller()

継承をうまいことやってあげたとする。Javaと同じ呼び出し方をしても、親を呼び出した時点でレシーバが親のレシーバに変化してしまう。最終的にはGrandParent#callerはGrandparent#nameを実行する。

※というかこういう継承関係をとれるライブラリがほとんどないわけだが。

Page 15: Web技術勉強会 20110723

call, applyを使うと同じことはできる

� 同じことは実はできる

child.super().super().caller()

super.apply(child)

child.super().caller()child.super().caller()

super.apply(child)

caller.apply(child)

child.caller()

Page 16: Web技術勉強会 20110723

call, applyを使うと同じことはできる

� call, applyは外部のオブジェクトを第一引数のオブジェクトとして呼び出すことができる神メソッド。

� callとapplyの違いは第二引数で渡す引数の形式の違い。

� callは個別に渡す(いくつでも渡せる)

� applyは配列で渡せる(1つだけ)。argumentsをそのまま渡すときはこっちを使う。

� つまり、第一引数のオブジェクトをレシーバとして指定できる。指定しない場合は呼び出し先のオブジェクト。

� これを利用するとさっきJavaでやったことと同じになる。

Page 17: Web技術勉強会 20110723

ここでmix.jsの話にようやく入る� mix.jsでもまったく同じことが起きる

� 回避方法も全く同じで、call, applyを使う

�が、これはない!!!�

� なぜか。継承するたびに、レシーバを子供に戻す作業が毎回発生する。

� つまり、mix.jsを使う開発者に余計な手間をかけさせることになる。

� 普通、Javaなどと同じ挙動になるだろうと思うはず。でそのとおりにならないのでバグになる、と。

� ライブラリとは、こういうことを全部よしなにやってあげるためにあるわけで。故にこれはない。

Page 18: Web技術勉強会 20110723

要するに今回の内容は

� 親メソッドでthisを使っている場合、自動的にcall, applyをラップしてあげるよ、という実装の話。

� 動作イメージ

� var Psp = Module.create({

� name “psp”,

� getName: function() { return this.name; }� getName: function() { return this.name; }

� });

� var PsVita = Module.create({

� name: “psvita”,

� getName: function() { return this.parent.name; }

� });

� var obj = PsVita.mix(Psp);

� obj.getName(); // psvita

Page 19: Web技術勉強会 20110723

とりあえずなにも考えずに実装

� これまでは親を「parent」プロパティで参照していた

� obj.parent.getName();

� parentを挙動を変えて実装してみた。

� が、失敗。

� 常に子をレシーバにして呼び出すことはできたが、既存の処理が正常に動作しなくなった。正常に動作しなくなった。

� 具体的にはhasメソッド。テストが軒並みこけるように。

� 一旦白紙に戻し方針転換。

� いっそ、既存の処理に影響がでないようにプロパティを新たに追加することにした。

Page 20: Web技術勉強会 20110723

parentと__parent__

� あらたに「__parent__」を定義。

� parentは「外部用親参照プロパティ」と定義

� __parent__は「内部用親参照プロパティ」と定義

� parent経由で親を参照するとレシーバは常に子になる

� __parent__経由で親を参照するとレシーバは現在参照しているオブジェクト(モジュール)。いるオブジェクト(モジュール)。

� ルール上、__parent__の仕様は非推奨とした。

� __parent__はあくまで「内部」で使う目的で定義したため

� 「__」をつけているのは通常のプロパティとは違う、という意味を含めている。

� mix.jsの作り上、特定のプロパティを非公開(private)にはできないので、参照自体は可能。

Page 21: Web技術勉強会 20110723

実装の肝

� 実装の肝はどうやってレシーバを子に変換するか。

� parentに継承したモジュールのメソッドをチェーンさせるときに、メソッドをfor-inでバラして、メソッドをフックする。

// これまではだいたいこんな感じfor (var prop in parent) {for (var prop in parent) {

child[prop] = parent[prop];

}

// 今はこんな感じfor (var prop in parent) {

child[prop] = hook(child, parent[prop]);

}

Page 22: Web技術勉強会 20110723

実装の肝

� applyを使ってレシーバを子供にしつつ、処理自体は委譲してあげる

function hook(self, f) {

return funciton() {

f.apply(self, arguments);

};

}

してあげる

Page 23: Web技術勉強会 20110723

この機能によって得られる物

� 同じような画面がある場合、ほとんどの処理を委譲できるためコード量を大幅に削減可能

� 処理が似ているけどちょっとだけ違う場合

� オーバーライドが可能なので加工処理などを行える

� 処理が全く同じ場合

� サブモジュール(サブクラスに相当)にメソッド定義しなくてすむ=そ� サブモジュール(サブクラスに相当)にメソッド定義しなくてすむ=そのままスーパーモジュール(スーパークラスに相当)を「自動的に」コールするため不要

� この機能によって一般的なOOPと同じ利点が得られる

Page 24: Web技術勉強会 20110723

まとめ

� mix.jsに内部親参照用プロパティ「__parent__」、外部親参照用プロパティ「parent」を実装

� コードの重複を排除できるようになった

� オーバーライドを自然にできるようになった

� 今後は

� 実装はほぼ終了� 実装はほぼ終了

� mix.js用モジュールはつくるかもしれない

� Webアプリケーションに適用して評価する