皆さん、こんにちは。
第 3 講目へようこそ。
今までに、継承や self
メッセージ探索を見てきました。 では super を見てみましょう。
最初に、super がレシーバーとなるような
メッセージ送信を見てみましょう。
super とは何でしょう？
ちょっと時間をかけて 次の2点について定義を考えてみましょう。
super は何を表わすのでしょうか？
super へメッセージが送られた時
メソッドはどのように探索されるでしょうか？
次の原則は
Pharo でも Java でも同じです。
ここで見ている例は
前の講義で見たものに似ています。
super が1つのメソッドで使われています。
何が起こるか、考えてみましょう。
A new bar については
問題ありません。
前と同じプロセスです。
メソッドが実行されて
foo は 10 を返します。
B new bar の場合について ステップ毎に見ていきましょう。
メッセージ bar を
オブジェクト B new に送ります。
探索アルゴリズムはメソッド bar を
そのオブジェクトのクラスから 探していきます。
メソッド bar が見つかります。
そのメソッドがレシーバの上で実行されます。
この bar メソッドが メソッドの上で実行されます。
ここで、「super bar」を 計算しなければなりません。
そして「self foo」も実行して 両者を足し合わせます。
「self foo」はできますね。
「self foo」はオブジェクト self に メッセージ foo を送ります。
self は B new です。
可能なのは、クラス A の foo メソッドだけです。
したがって、 self foo は 10 を返します。
「super bar」では
super がレシーバーです。
探索アルゴリズムは
レシーバー super があることで 変わってきます。
探索アルゴリズムは キーワード super を含んでいるクラスの
スーパークラスから メソッド bar を探し始めます。
super は、クラス B の bar メソッドにあります。
メソッド bar を探す開始点は
B のスーパークラスである A になります。
したがって、このメソッドが見つかります。 そして実行されます。
foo が self に送られます。
self は常にレシーバーオブジェクトです。
つまり B new です。
したがって foo は 最初のオブジェクトに送られます。
これが実行された foo です。 10 が返されます。
10 + 10 は 20 です。
C new bar の場合には
ここに C new とあります。
それはクラス C のインスタンスです。 それにメッセージ bar を送ります。
クラス C で bar を探します。
見つかりません。 上位のクラスが探索されます。
B で見つかります。
それで B の bar が実行されます。
この bar は 2 つのメッセージ送信の合計です。
2つ目から始めてみます。
メッセージ foo を オブジェクト self へ送ります。
self はまだ C new です。 メッセージ foo を送ります。
メソッドが見つかって、50 が返されます。
ここに 50 を置いておきます。
ここで、1つ目の部分である super bar が必要になります。
メッセージ bar をsuper に送ります。 super は常にレシーバーです。
しかし探索アルゴリズムが変わります。
探索アルゴリズムはsuper を含むクラスの スーパークラスから bar を探します。
それは B クラスの bar メソッドです。
アルゴリズムは B のスーパークラスである A から bar を探し始めます。
bar はここにあります。
この bar が実行されます。
self はまだ C new です。 まだこのままです。
いつでもレシーバーです。
メッセージ foo を送ります。
結果は 50 です。ここに 50 が得られます。
50 + 50 は 100 です。
続くスライドで
探索アルゴリズムのより詳細を見て
深く学びましょう。
super は常にレシーバーを指します。
その点ではまさに self や
Java の this と似ています。
同様に、Java の super も this に似ています。 そして常にレシーバーを指します。
しかし、super にメッセージが送られた時には 探索アルゴリズムが変わって
実行されているメソッドを持つクラスの スーパークラスから
メソッドを探し始めます。
従って、self と super は根本的に異なります。
self は動的ですが super は静的です。
さらに説明をすると
foo が self に送られた時には
開発者は どの foo メソッドが実行されるか 全くわかりません。
同じクラスの foo かもしれませんし
今あるサブクラスの foo かもしれませんし
他の開発者がそのプログラムを実行する
直前に作った foo かもしれません。
bar メソッドの開発者が self foo と書く時には
その開発者には どの foo が実行されるか わからないのです。
これは便利な機能です。
つまり、開発者は新しいサブクラスを作って
クラス A の振る舞いを 変えることができるのです。
対照的に、super は静的です。
開発者が super foo と書く時には
プログラムを動かしたら どの foo メソッドが実行されるであろうか
わかります。
super foo とここに書く時には
スーパークラス A の foo を指すのです。
これが変わることはありません。 super は静的なのです。
プログラムがどうコンパイルされるか わかります。
残念なことに、書籍の中には
super に間違った定義を しているものがあります。
ナンセンスな定義です。
ある本にあった定義はこうです。
その本では、super は
探索アルゴリズムに
レシーバーのクラスのスーパークラスから メソッドを探すように指示する。
レシーバーのクラスのスーパークラス、と。
実際には、これは間違いです。 さきほど示した例で明らかなように。
aC をレシーバーとした場合には そのクラスは C です。
したがって、レシーバーのスーパークラスとは B のことになります。
ここにきて super foo を実行する時には
もしその本に書いてあった定義の通りだとすると
メッセージ foo を
super に送って実行されるのは
レシーバーのクラスのスーパークラスの foo メソッドだということになります。
つまり、これです。
foo を super に
何度も何度も送って 無限ループになります。
つまりその定義は間違いです。
実際には、この例は完璧に動きます。
単純に、この定義が間違っているのです。
覚えておくべきことは
self は常にレシーバーを表わします。
super も同様です。
Java での this と super と同じです。
しかし、super はメソッド探索を変えます。
マッチを探し始めるのが
キーワード super を含んでいる メソッドのクラスの
スーパークラスになります。
self は動的です。
開発者は self を使って 既存のクラスの振る舞いを拡張することができます。
メソッドを再定義するサブクラスを作ることで。