Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PHPとJavaScriptにおけるオブジェクト指向を比較する

5,340 views

Published on

〔PHPカンファレンス2013 発表資料〕PHPとJavaScriptはどちらもオブジェクト指向言語ですが、PHPはクラスベース、JavaScriptはプロトタイプベースという違いがあります。本発表では、クラスベースとプロトタイプベースがどのように違うのかを、仕組みから詳しく解説します。またプロトタイプベース特有のパターンについても説明します。 対象:初級〜中級

Published in: Technology
  • Be the first to comment

PHPとJavaScriptにおけるオブジェクト指向を比較する

  1. 1. copyright(c) 2013 kuwata-lab.com all rights reserved. PHPとJavaScriptにおける オブジェクト指向を比較する makoto kuwata <kwa@kuwata-lab.com> http://www.kuwata-lab.com/ 2013-09-14 (Sat) PHPカンファレンス2013 【更新履歴】 ・2013-09-17: 大幅に加筆修正
  2. 2. copyright(c) 2013 kuwata-lab.com all rights reserved. 本発表について • PHPでのオブジェクト指向の仕組みを理解する • JavaScriptでの(同上) • Class BaseとPrototype Baseの違いを理解する • オブジェクト指向言語の基礎知識があること (クラス、インスタンス、メソッド、etc) • JavaScriptを書いた経験があること • 独自の解釈が登場!自分で考えて判断を! 【目的】 【前提】 【注意】
  3. 3. copyright(c) 2013 kuwata-lab.com all rights reserved. オブジェクト指向機能の仕組み (PHP編) Internal Mechanism of OOP in PHP
  4. 4. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } オブジェクト指向機能の肝であるメソッド呼び出しの仕組みを説明します。
  5. 5. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } 変数はポインタであり、インスタンスオブジェクトを参照しています。
  6. 6. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } インスタンスオブジェクトには、クラスオブジェクトへの隠しポインタがあります。
  7. 7. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } クラスオブジェクトは、メソッド探索テーブルへのポインタを持っています。
  8. 8. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } メソッド名をキーにしてメソッド関数を探し、もしあれば実行します。
  9. 9. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } もしメソッド名が見つからなければ、親クラスを ります。
  10. 10. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } そこでも同じように、メソッド探索テーブルを使ってメソッドを検索します。
  11. 11. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } 最後にルートクラスでもメソッドが見つからなければ、実行時エラーとなります。
  12. 12. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 function () { ..... } Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } このように、メソッドは呼び出し時に「探索」される点が、関数と決定的に違います。 オブジェクト指向の柔軟性はこれのおかげですが、関数より遅い原因でもあります。
  13. 13. copyright(c) 2013 kuwata-lab.com all rights reserved. インスタンス変数とインスタンスメソッド クラス x: 15 y: 25 メソッド表 m4 m2 m3 Z: 456 function () { ..... } インスタンス変数はインスタン スのもの(共有されない) インスタンスメソッドは クラスのもの(共有される) メソッド関数 インスタンス x: 10 y: 20 x: 30 y: 40
  14. 14. copyright(c) 2013 kuwata-lab.com all rights reserved. is-aポインタ 所属するクラスオブジェクトを指す隠しポインタ◆ つまりインスタンスは、「自分が何者かを知っている」 x: 10 インスタンス クラス 変数 y: 20 is-a ポインタは is-a 関係を表す
  15. 15. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド探索テーブル メソッドテーブル m1 m2 m3 function () { echo "hello"; } function ($x) { return $x + 1; } クラス メソッド名 + メソッド関数へのポインタ◆ メソッド名をキーにして、メソッド関数が動的に検索される (注) (注)正確には「メソッドシグニチャ」(詳細は省略)
  16. 16. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッドオーバーライドと parent 呼び出し 変数 インスタンス クラス クラス x: 10 y: 20 メソッド表 メソッド表 m4 m2 m3 m3 m1 m2 Z: 123 Z: 456 メソッド関数 function () { ..... } function () { ..... } 同じ名前で別の関数を 登録するのがオーバーライド 自クラスをスキップして 親クラスから探索するのが parent 呼び出し メソッド関数
  17. 17. copyright(c) 2013 kuwata-lab.com all rights reserved. オブジェクト指向機能の仕組み (JavaScript編) Internal Mechanism of OOP in JavaScript
  18. 18. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト JavaScript におけるメソッド呼び出しの仕組みを説明します。
  19. 19. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 変数はオブジェクトを指しています。そこにプロパティがあるかを調べます。
  20. 20. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 指定されたプロパティがなければ、別のオブジェクトをたどります。
  21. 21. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 見つかったプロパティが関数であれば、メソッドとして実行できます。
  22. 22. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 見つからなければ、さらに別のオブジェクトをたどって、
  23. 23. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 同じようにプロパティを探します。
  24. 24. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト 最後までたどっても見つからなかれば、(JSでは) undefined が返されます。
  25. 25. copyright(c) 2013 kuwata-lab.com all rights reserved. メソッド呼び出し 変数 オブジェクト オブジェクト オブジェクト x: 10 y: 20 function () { ..... } m2: Z: 456 関数オブジェクト function () { ..... } m1: m2: 関数オブジェクト このように、JavaScriptの動作もPHPとよく似ています。ただし、クラスやメソッド 探索テーブルは存在せず、すべてオブジェクトで済ませているのが特徴です。
  26. 26. copyright(c) 2013 kuwata-lab.com all rights reserved. __proto__ プロパティ プロパティが自分にないときに探しに行く先◆ is-a ポインタに相当するが、任意のオブジェクトを自分で指定可能 height: 15 オブジェクト変数 obj __proto__: height: 0 オブジェクト width: 0 __proto__: width: 30 color: 'red' obj.color == 'red' (注) (注)IE9 以前では利用不可
  27. 27. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプチェーン プロパティが見つかるまで __proto__ をたどる◆ 最後までたどっても見つからなければ undefined を返す y: 20 オブジェクト __proto__: x: 10 オブジェクト y: 10 __proto__: x: 0 オブジェクト y: 0 __proto__: z: 0探してるのが 「y」ならこれ が使われる 「x」ならこれ が使われる 「z」ならここ まで探しにくる
  28. 28. copyright(c) 2013 kuwata-lab.com all rights reserved. コンストラクタ function Foo(x) { this.x = x; } Foo.prototype.value = 100; Foo.prototype.hello = function() { ... }; prototype: 関数オブジェクト name: 'Foo' __proto__: 変数 Foo 関数オブ ジェクト value: 100 hello: __proto__: オブジェクト __proto__ と .prototype は 別であることに注意
  29. 29. copyright(c) 2013 kuwata-lab.com all rights reserved. new 演算子 var obj = new Foo(123); obj.hello(); x: 123 オブジェクト __proto__: 変数 obj prototype: 関数オブジェクト name: 'Foo' __proto__: 変数 Foo 関数オブ ジェクト value: 100 hello: __proto__: オブジェクト 空オブジェクトを生成、 __proto__ を設定、 コンストラクタを適用
  30. 30. copyright(c) 2013 kuwata-lab.com all rights reserved. __proto__ と prototype の違いがわかりません! Foo.__proto__ は、Foo という(関数)オブジェ クトのためのもの ◆ new で新規作成するオブジェクトには、何も関係しない Foo.prototype は、new で新規作成するオブジェ クトのためのもの ◆ Foo 関数自身のプロトタイプチェーンには、何も関係しない
  31. 31. copyright(c) 2013 kuwata-lab.com all rights reserved. クラスベース (PHP) と プロトタイプベース (JS) Class Base v.s. Prototype Base
  32. 32. copyright(c) 2013 kuwata-lab.com all rights reserved. 北の賢者曰く プロトタイプベースにいくつかの制約を加えたのが クラスベースである。 つまり、クラスベースはプロトタイプベースのサブ セットである。 “ ” http://sumim.no-ip.com/wiki/493 (「超約」してるので原文を確認のこと) by sumim
  33. 33. copyright(c) 2013 kuwata-lab.com all rights reserved. is-a ポインタ v.s. __proto__ プロパティ $obj->isa = new Other(); // できない is-a ポインタは、プロブラマが変更できない◆ そもそもプログラマには見えない obj.__proto__ = new Other(); // できる! __proto__ プロパティは、自由に変更できる◆ 任意のオブジェクトを設定できる
  34. 34. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプベース + 制約 = クラスベース クラスベース (PHP) プロトタイプベース (JS) 探索 ポインタ ポイント対象 ポインタ先 クラスチェーン プロトタイプチェーン is-a ポインタ __proto__ プロパティ クラスオブジェクトのみ 任意のオブジェクト 変更不可 変更可能 制約がない制約がある! “プロトタイプベースにいくつかの制約を 加えたのがクラスベースである。”
  35. 35. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプベース ⊃ クラスベース プロトタイプベース クラスベース 両者は、排他関係ではなく包含関係◆ 制約がある分、クラスベースのほうが「狭い」 “クラスベースはプロトタイプベースの サブセットである。”
  36. 36. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプベースの 応用パターン Advanced Pattern of Prorotype Base
  37. 37. copyright(c) 2013 kuwata-lab.com all rights reserved. 設定ファイル用オブジェクト default (共通) 共通する設定を一元管理◆ 開発用と本番用は差分だけを管理 development (開発用) __proto__: user: "your-name" production (本番用) __proto__: user: "wpadmin" pass: "bh#un?j9" __proto__: host: "localhost" port: 3306 user: "" pass: "" db: "wordpress"
  38. 38. copyright(c) 2013 kuwata-lab.com all rights reserved. 追記型オブジェクト 一度公開したオブジェクトは一切変更しない◆ 変更するかわりに、差分だけのオブジェクトを作り (注)、その __proto__ にもとのオブジェクトを設定。 (注) 値の削除は、undefined で表現する。 __proto__: color: "red" version: 3 __proto__: height: 125 version: 4 __proto__: width: 55 version: 2 __proto__: height: 120 width: 50 color: "pink" version: 1 created: "09/14"
  39. 39. copyright(c) 2013 kuwata-lab.com all rights reserved. DCI (Data, Context, Interaction), Decorator Pattern __proto__: name: "○×社" addr: "東京" phone: "03-..." created: "09/14" __proto__: 納期問合せ() 発注処理() 発注先 (Role) __proto__: 納期回答() 受注処理() 受注先 (Role) 取引先 (Player) インスタンス単位での拡張や追加がとても自然◆ 「差分プログラミング」がクラス単位ではなくインスタンス単位に 「受注先」と して機能する 「取引先」 「受注先」かつ 「発注先」であ る「取引先」 (あとで詳しくやります…)
  40. 40. copyright(c) 2013 kuwata-lab.com all rights reserved. よくある誤解 プロトタイプベースは面倒、クラスベースのほ うが簡単で便利!〔PHP派による誤解〕 ◆ 制約がない分、プロトタイプベースのほうが自由度は高い。 また、JavaScriptのクラス定義が面倒なのはJavaScriptの文法がクソ なせいであって、プロトタイプベースのせいではない。 JSにクラス構文が入ったら、プロトタイプベー スの特徴が失われてしまう!〔JS派による誤解〕 ◆ 糖衣構文が入っても、見た目が変わるだけで、プロトタイプベース であることは変わらない。それよりも __proto__ の標準化が遅れて いることを心配すべき(そのせいで JS はプロトタイプベースの利 点を活かせていない)。
  41. 41. copyright(c) 2013 kuwata-lab.com all rights reserved. 委譲と継承と プロトタイプチェーン Delegation, Inheritance, and Prototype Chain
  42. 42. copyright(c) 2013 kuwata-lab.com all rights reserved. 委譲 機能の一部 or 全部を、他のオブジェクトに任せ ること(丸投げ!) class Foo { private $m1; function __constructor() { $this->other = new OtherObj(); } function hello(arg) { $this->other->hello(arg); } .... 丸投げ!
  43. 43. copyright(c) 2013 kuwata-lab.com all rights reserved. 委譲と継承はよく似ている ## 継承は、メソッド探索を自動で行ってくれる $this->isa->methodtbl->hello(...); ## 委譲は、メソッド探索を自力で行っている $this->other->hello(...); ◆ ◆ 委譲は、「継承の自力版」と見なせる 言語サポートはないかわりに、複数の移譲先が可能、変更も可能 継承は、「委譲の限定版」と見なせる 移譲先は1つだけで変更も不可だが、言語サポートがあるので便利 (注)独自の見解です
  44. 44. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプチェーン ## 委譲と同様に、探索先を自分で設定可能 this.__proto__ = new Foo(); ## 継承と同様に、自動的に探索してくれる $this.hello(); $this.__proto__.hello(); $this.__proto__.__proto__.hello(); ◆ プロトタイプチェーンは、委譲と継承の間の子 委譲の柔軟性と、継承の利便性を合わせ持つ (注)独自の見解です
  45. 45. copyright(c) 2013 kuwata-lab.com all rights reserved. 継承では $this が変わらない 1: class Foo { 2: function hello() { ... } 3: function main() { 4: ...; $this->hello(); ...; 5: } 6: } 7: class Bar extends Foo { 8: function hello() {...} // override 9: // main() はそのまま 10: } 11: $obj = new Bar(); 12: $obj->main(); $this は Bar オブ ジェクトのまま! オーバーライドした Bar#hello() が最終的に 呼び出される 難しい話なので、分から なければ読み飛ばして!
  46. 46. copyright(c) 2013 kuwata-lab.com all rights reserved. 委譲では $this が変わる 1: class Foo { 2: function hello() { ... } 3: function main() { 4: ...; $this->hello(); ...; } 5: } 6: class Bar extends Foo { 7: function hello() { ... } 8: function main() { 9: $this->foo->main(); } 10: } 11: $obj = new Bar(); 12: $obj->foo = new Foo(); 13: $obj->main(); この $this は Bar ではない! せっかく Bar#hello() を 新しく定義したのに 呼び出されない! (Decorator patternでありがち) Foo オブジェクト が $this になる! 難しい話なので、分から なければ読み飛ばして!
  47. 47. copyright(c) 2013 kuwata-lab.com all rights reserved. 対策: $this を引数として渡す class Foo { function hello() { ... } function main($_this=null) { if ($_this === null) $_this = $this; ...; $_this->hello(); ...; } } class Bar { var $foo = new Foo(); function hello() { ... } function main() { $this->foo->main($this); } } こんな面倒なことを 全メソッドで行うの? この用意をしてないクラス は委譲先にできないの? 難しい話なので、分から なければ読み飛ばして!
  48. 48. copyright(c) 2013 kuwata-lab.com all rights reserved. プロトタイプチェーンでは this が変わらない 1: function Foo() {} 2: Foo.prototype.hello = function() {...}; 3: Foo.prototype.main = function() { 4: ...; this.hello(); ...; 5: }; 6: 7: function Bar() {} 8: Bar.prototype.hello = function() {...}; 9: 10: var obj = new Bar(); 11: obj.__proto__ = new Foo(); 12: obj.main(); この this は Bar オ ブジェクトのまま! 委譲と同じことしてるのに、 Bar#hello() が呼ばれる! (Decorator pattern大勝利の気配!)
  49. 49. copyright(c) 2013 kuwata-lab.com all rights reserved. 比較表 継承 プロトタイプ チェーン 委譲 言語サポート 利便性 委譲先数 委譲先 $this あり あり なし 便利 便利 煩雑 1つだけ 1つだけ 複数指定可能 変更不可 変更可 変更可 変わらない 変わらない 変わる - 継承をより柔軟にしたもの - 委譲の煩雑さを解消したもの
  50. 50. copyright(c) 2013 kuwata-lab.com all rights reserved. クラス設計 改善例 An Example to Improve Class Design
  51. 51. copyright(c) 2013 kuwata-lab.com all rights reserved. 間違ったクラス設計例 class 受注先 { var $name; var $addr; function 納期回答(); } class 発注先 { var $name; var $addr; function 納期問合わせ(); } 「受注先かつ発注先」なら両方に登録が必要◆ 実体は同じなのに一元管理されないため、名寄せが必要 自称DB設計上級者に ありがちな間違い
  52. 52. copyright(c) 2013 kuwata-lab.com all rights reserved. 間違ったクラス設計例 class 取引先 { var $name; var $addr; } class 受注先 extends 取引先 { function 納期回答(); } class 発注先 extends 取引先 { function 納期問合わせ(); } class 受発注先 extends 受注先,発注先 { } 「受注先が発注先にJob Change!」を表せない◆ 一度作ったオブジェクトのクラスは変更できないせい 自称Java上級者に ありがちな間違い ダイヤモンド継承!
  53. 53. copyright(c) 2013 kuwata-lab.com all rights reserved. 好ましいクラス設計例 class 取引先 { // Player var $name; var $addr; var $受注先 = new 受注先(); var $発注先 = new 発注先(); } class 受注先 { // Role function 納期回答(); } class 発注先 { // Role function 納期問合わせ(); } 「Player」と「Role」とを分け、委譲を使う◆ 「受注先かつ発注先」も「Job Change!」も自然に表現可能 でも、受注先や発注 先って、対応する取 引先オブジェクトが 必要だよね?
  54. 54. copyright(c) 2013 kuwata-lab.com all rights reserved. より好ましいクラス設計例 class 取引先 { ... } class Role { var $player; function __constructor($player) { $this->player = $player; } } class 受注先 extends Role { ... } class 発注先 extends Role { ... } 「Role」が「Player」を保持する(さっきと逆)◆ Role には Player が必要だが、Player には Role は必須ではない Role が Player を保持 (なぜなら Role には Player が必要だから)
  55. 55. copyright(c) 2013 kuwata-lab.com all rights reserved. 若干の問題点 // 受注先である取引先 $player = new 取引先('○ 商会'); $role = new 受注先($player); // 受注先として扱う echo $role->納期回答(); // 取引先として扱う echo $role->player->name; echo $player->name; Role と Player で使い方に差がある◆ Player の属性やメソッドに Role からアクセスするとき Role からは、Player の属性へ 直接にはアクセスできない
  56. 56. copyright(c) 2013 kuwata-lab.com all rights reserved. そこでプロトタイプベース! class 取引先 { ... } class Role { function __constructor($player) { $this->player = $player; $this->__proto__ = $player; } } class 受注先 extends Role { ... } class 発注先 extends Role { ... } Role をあたかも Player のように扱える◆ Decorator Pattern も DCI もいらんかったんや! 仮に PHP でこれが 可能だとすると…
  57. 57. copyright(c) 2013 kuwata-lab.com all rights reserved. そこでプロトタイプベース! // 受注先 (Role) の役割をもった取引先 (Player) は、 $player = new 取引先('○ 商会'); $role = new 受注先($player); // Role としても Player としても扱える echo $role->納期回答(); echo $role->name; // 複数の Role を重ねることさえ可能 $role = new 発注先($role); // 受注先兼発注先 Role をあたかも Player のように扱える◆ Decorator Pattern も DCI もいらんかったんや! Role なのに、まるで Player のように扱える!
  58. 58. Any Questions?
  59. 59. おしまい

×