Rubyにおける継承はJavaにおける継承と異なる細かな点がいくつもあったのでまとめる必要があった。
この記事はO'ReillyのRuby本を参考にしている。
まず、基本型。
「Point」をスーパークラスとするサブクラス「Point3D」を宣言する方法は以下の通り。
これからPointで宣言された以下の六つの要素がどのように、Point3Dに継承されるのか(あるいは継承されないのか)を一つずつ見ていく。
1、インスタンスメソッド
2、クラスメソッド
3、インスタンス変数
4、クラス変数
5、クラスインスタンス変数
6、定数
1、インスタンスメソッド
インスタンスメソッドは「全て」継承する。
ここで「全て」と言っているのはinitializeメソッドも含めるということを意味する。
Javaではコンストラクタは継承されず、サブクラスで定義し直す必要があったが、
Rubyではinitializeメソッドを例外とせず、通常のインスタンスメソッドと同じように扱い、そのまま継承される。
なお、継承された全てのインスタンスメソッドは上書き(override)できる。
上書きするインスタンスメソッドについて、サブクラスの定義の中でsuperメソッドを使うと、スーパークラスの同名メソッドを呼び出すことができる。
これはinitializeをオーバーライドする時にはほぼ必須のテクニックである。
2、クラスメソッド
クラスメソッドは、インスタンスメソッドと同じように継承できる。
上書き(override)もできるし、サブクラスの定義の中でsuperメソッドを使うと、スーパークラスの同名クラスメソッドにアクセスできる点も同様。
しかし、注意しておきたいことがある。
一般にクラスメソッドは「ある一つのクラスに固有のメソッド」と考えるのが自然である。
つまりPointのクラスメソッドsumを呼び出したいなら、Point.sumを使うべきで、
内容が全く同じでも、継承されたPoint3D.sumを使うべきではない(使えたとしても)。
このように、レシーバを明示してクラスメソッドを呼び出すときには、継承されたクラスメソッドには頼らない方が良い。
(ここでいうレシーバというのはPointであり、レシーバを明示したクラスメソッドの呼び出しというのはPoint.sum)
クラスメソッドは、メソッドを定義したクラス(Point.sum)を介して呼び出すべきである。
ではレシーバを明示しないでクラスメソッドを呼び出すときというのはどんな時であろうか。
レシーバを明示しないでクラスメソッドを呼び出すことが可能なのか。
とも思われるかもしれないが、そのような場合があるのだ。
それは、クラスメソッド本体中において、「他の」クラスメソッドを呼び出す場合である。例を見てみよう。
ここでPoint.puts_classをPoint3Dは継承し、クラスメソッドPoint3d.puts_classを持つ。
スーパークラスのクラスメソッドPoint.puts_classを呼び出したときのselfは「Point」であり
継承されたクラスメソッドPoint3D.puts_classを呼び出したときのselfは「Point3D」となる。
つまりselfはレシーバによって動的に形を変えるのだ。
継承されるクラスメソッドが、サブクラスで利用される(サブクラスをレシーバとして呼び出す)ことが想定される場合は
このようにselfを使って動的に動作が変わるものであることが大半だろう。
だが例外としてObject.newは、ほとんどのオブジェクトに継承され、そのクラスを介して呼び出される。
3、インスタンス変数
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
これは私も含めたJavaプログラマへの啓蒙である。
では詳しく説明しよう。
以下のコードではPoint3DはPointのインスタンス変数@x,@yを継承し、
独自インスタンス変数@zを持っているかのように見える。
しかし、それは正しい捉え方ではない。
Rubyにおける変数は、代入した時点で初めて変数が作られる。
つまり代入がおこらなければ、変数は存在しないのだ。
Point3D.new(1,1,1)を呼び出すと、Point3Dのインスタンスメソッドinitializeが起動され、
内部でsuper(1,1)を呼び出す。するとPointのインスタンスメソッドinitializeが起動され、
@x,@y = 1,1の代入が行われる。
その後、Point3Dのインスタンスメソッドに戻り@z = 1の代入が行われる。
このようにして、インスタンス変数は作られている。
Point3Dが継承した(実際はさらに上書きした)のはinitializeメソッドだけであり、
initializeのsuperを通して、インスタンス変数@x,@yが作られている。
このことで、あたかもインスタンス変数が継承されたかのように見えるが
実際のインスタンス変数は継承のメカニズムとは全く無関係なのである。
Pointは@x,@yの二つのインスタンス変数しか持たないので、これらは明確だが
内部の実装が分からないクラスを拡張したサブクラスにおいて、インスタンス変数を新たに定義するときは要注意だ。
例えば、Pointが内部のinitializeで@statusを宣言していて、この@statusを元にメソッドの動作が変わるとしよう。
そこで、このPointを継承して拡張しようとするユーザがこの@statusというインスタンス変数があることを知らずに、
自分で独自に使う機能をつけるために@statusという変数を使おうとした場合、initializeメソッドでsuperの後に@statusへの代入文を書くと、
祖先のインスタンス変数を上書きしてしまう。
その場合、祖先から継承したメソッドの動作が変わってしまうということが起こりうる。
このようなことからRubyのサブクラス化はスーパークラスの実相がよくわかっている場合を除いて危険だと考える理由の一つになっている。
4、クラス変数
クラス変数は継承される。
具体的にはクラスとそのすべてのサブクラスによって「共有」されるという形を取る。
これはつまり、サブクラスでクラス変数を変更すると、
スーパークラスの同名クラス変数も変更されてしまう。
5、クラスインスタンス変数
クラスインスタンス変数は継承されない。
クラスインスタンス変数は、インスタンス変数と紛らわしいが以下の@a,@bのように宣言する。
@x,@yがPointクラスのインスタンス(例:Point.new(1,1))のインスタンス変数だとすると
@a,@bはClassクラスのインスタンス(Point)のインスタンス変数である。
Pointをスーパークラスとするクラスが継承するのはPointクラスの実装、いわば設計図であり、
Classクラスの(設計図から作られた)一つのインスタンス(Point)がどのような変数を持っていても何も関係しないのだ。
つまり、そもそもクラスインスタンス変数は継承のメカニズムとは無関係である。
6、定数
定数は継承する。
定数もインスタンスメソッドと同様に、継承され、オーバーライドできる。
が、定数はインスタンスメソッドのように動的ルックアップが行われない。
(動的ルックアップというのは、同名の関数が定義されている場合、一番近くで定義されたものを動的に採用するというもの)
優先されるのは、定数が使われている場所(メソッド)と同じスコープである。
その後、継承階層で探索が行われるが、これは抽象クラスにおける抽象メソッドの呼び出しのようなものになる。
testメソッドの定義はPointクラスにあり、内部でインスタンスメソッドgreetingと定数ORIGINを参照する。
p.testではgreetingもORIGINも、testメソッドと同じスコープから探し出す。
q.testでは動的ルックアップによりPoint3Dのgreetingを採用し、GoodAfternoonを返すが、
定数は、その定数を使用しているメソッド(ここではtest)と同じ場所(Pointクラス)から優先して探し出す。
そこで採用されるのがPoint::ORIGINというわけだ。
以上が、Rubyの継承メカニズムにおける六つの要素の挙動である。
この記事はO'ReillyのRuby本を参考にしている。
まず、基本型。
「Point」をスーパークラスとするサブクラス「Point3D」を宣言する方法は以下の通り。
#super class
class Point
#ここはPointクラスの実装(implementation)
#メソッド(インスタンスメソッド・クラスメソッド),
#変数(インスタンス変数・クラス変数・クラスインスタンス変数),
#定数
#などが入る
end
class Point3D < Point
#ここはPoint3Dクラスの実装(implementation)
end
これからPointで宣言された以下の六つの要素がどのように、Point3Dに継承されるのか(あるいは継承されないのか)を一つずつ見ていく。
1、インスタンスメソッド
2、クラスメソッド
3、インスタンス変数
4、クラス変数
5、クラスインスタンス変数
6、定数
1、インスタンスメソッド
インスタンスメソッドは「全て」継承する。
ここで「全て」と言っているのはinitializeメソッドも含めるということを意味する。
Javaではコンストラクタは継承されず、サブクラスで定義し直す必要があったが、
Rubyではinitializeメソッドを例外とせず、通常のインスタンスメソッドと同じように扱い、そのまま継承される。
class Point
def initialize(x,y)
@x,@y = x,y
end
def to_s
"(#@x,#@y)"
end
end
class Point3D < Point
#何も定義しない
end
p = Point.new(1,1)
q = Point3D.new(2,2)
puts p #=> (1,1)を表示
puts q #=> (2,2)を表示
なお、継承された全てのインスタンスメソッドは上書き(override)できる。
上書きするインスタンスメソッドについて、サブクラスの定義の中でsuperメソッドを使うと、スーパークラスの同名メソッドを呼び出すことができる。
これはinitializeをオーバーライドする時にはほぼ必須のテクニックである。
class Point
def initialize(x,y)
@x,@y = x,y
end
def to_s
"(#@x,#@y)"
end
end
class Point3D < Point
def initialize(x,y,z)
super(x,y)
@z = z
end
def to_s
"(#@x,#@y,#@z)"
end
end
p = Point.new(1,1)
q = Point3D.new(2,2,2)
puts p #=> (1,1)を表示
puts q #=> (2,2,2)を表示
2、クラスメソッド
クラスメソッドは、インスタンスメソッドと同じように継承できる。
上書き(override)もできるし、サブクラスの定義の中でsuperメソッドを使うと、スーパークラスの同名クラスメソッドにアクセスできる点も同様。
しかし、注意しておきたいことがある。
一般にクラスメソッドは「ある一つのクラスに固有のメソッド」と考えるのが自然である。
つまりPointのクラスメソッドsumを呼び出したいなら、Point.sumを使うべきで、
内容が全く同じでも、継承されたPoint3D.sumを使うべきではない(使えたとしても)。
このように、レシーバを明示してクラスメソッドを呼び出すときには、継承されたクラスメソッドには頼らない方が良い。
(ここでいうレシーバというのはPointであり、レシーバを明示したクラスメソッドの呼び出しというのはPoint.sum)
クラスメソッドは、メソッドを定義したクラス(Point.sum)を介して呼び出すべきである。
ではレシーバを明示しないでクラスメソッドを呼び出すときというのはどんな時であろうか。
レシーバを明示しないでクラスメソッドを呼び出すことが可能なのか。
とも思われるかもしれないが、そのような場合があるのだ。
それは、クラスメソッド本体中において、「他の」クラスメソッドを呼び出す場合である。例を見てみよう。
class Point
#単純にクラス名を表示するクラスメソッド
def Point.puts_class
puts self.to_s #selfは省略可。省略された場合も、暗黙のうちにselfから呼び出される。
end
end
class Point3D < Point
end
puts Point.puts_class # => Pointを表示
puts Point3D.puts_class # => Point3Dを表示
ここでPoint.puts_classをPoint3Dは継承し、クラスメソッドPoint3d.puts_classを持つ。
スーパークラスのクラスメソッドPoint.puts_classを呼び出したときのselfは「Point」であり
継承されたクラスメソッドPoint3D.puts_classを呼び出したときのselfは「Point3D」となる。
つまりselfはレシーバによって動的に形を変えるのだ。
継承されるクラスメソッドが、サブクラスで利用される(サブクラスをレシーバとして呼び出す)ことが想定される場合は
このようにselfを使って動的に動作が変わるものであることが大半だろう。
だが例外としてObject.newは、ほとんどのオブジェクトに継承され、そのクラスを介して呼び出される。
3、インスタンス変数
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
Rubyでは、インスタンス変数は継承されない。
これは私も含めたJavaプログラマへの啓蒙である。
では詳しく説明しよう。
以下のコードではPoint3DはPointのインスタンス変数@x,@yを継承し、
独自インスタンス変数@zを持っているかのように見える。
しかし、それは正しい捉え方ではない。
class Point
def initialize(x,y)
@x,@y = x,y
end
def to_s
"(#@x,#@y)"
end
end
class Point3D < Point
def initialize(x,y,z)
super(x,y)
@z = z
end
def to_s
"(#@x,#@y,#z)"
end
end
Rubyにおける変数は、代入した時点で初めて変数が作られる。
つまり代入がおこらなければ、変数は存在しないのだ。
Point3D.new(1,1,1)を呼び出すと、Point3Dのインスタンスメソッドinitializeが起動され、
内部でsuper(1,1)を呼び出す。するとPointのインスタンスメソッドinitializeが起動され、
@x,@y = 1,1の代入が行われる。
その後、Point3Dのインスタンスメソッドに戻り@z = 1の代入が行われる。
このようにして、インスタンス変数は作られている。
Point3Dが継承した(実際はさらに上書きした)のはinitializeメソッドだけであり、
initializeのsuperを通して、インスタンス変数@x,@yが作られている。
このことで、あたかもインスタンス変数が継承されたかのように見えるが
実際のインスタンス変数は継承のメカニズムとは全く無関係なのである。
Pointは@x,@yの二つのインスタンス変数しか持たないので、これらは明確だが
内部の実装が分からないクラスを拡張したサブクラスにおいて、インスタンス変数を新たに定義するときは要注意だ。
例えば、Pointが内部のinitializeで@statusを宣言していて、この@statusを元にメソッドの動作が変わるとしよう。
そこで、このPointを継承して拡張しようとするユーザがこの@statusというインスタンス変数があることを知らずに、
自分で独自に使う機能をつけるために@statusという変数を使おうとした場合、initializeメソッドでsuperの後に@statusへの代入文を書くと、
祖先のインスタンス変数を上書きしてしまう。
その場合、祖先から継承したメソッドの動作が変わってしまうということが起こりうる。
このようなことからRubyのサブクラス化はスーパークラスの実相がよくわかっている場合を除いて危険だと考える理由の一つになっている。
4、クラス変数
クラス変数は継承される。
具体的にはクラスとそのすべてのサブクラスによって「共有」されるという形を取る。
これはつまり、サブクラスでクラス変数を変更すると、
スーパークラスの同名クラス変数も変更されてしまう。
class PointA
@@value = 1
def self.value; @@value; end
end
puts PointA.value #=>1を表示
class PointB < PointA; @@value = 2; end #サブクラスが共有クラス変数を変更
puts PointA.value #=>2を表示
class PointC < PointB; @@value = 3; end
puts PointB.value #=>3を表示
end
5、クラスインスタンス変数
クラスインスタンス変数は継承されない。
クラスインスタンス変数は、インスタンス変数と紛らわしいが以下の@a,@bのように宣言する。
class Point
@a = 0
@b = 0
def initialize(x,y)
@x,@y = x,y
end
end
@x,@yがPointクラスのインスタンス(例:Point.new(1,1))のインスタンス変数だとすると
@a,@bはClassクラスのインスタンス(Point)のインスタンス変数である。
Pointをスーパークラスとするクラスが継承するのはPointクラスの実装、いわば設計図であり、
Classクラスの(設計図から作られた)一つのインスタンス(Point)がどのような変数を持っていても何も関係しないのだ。
つまり、そもそもクラスインスタンス変数は継承のメカニズムとは無関係である。
6、定数
定数は継承する。
定数もインスタンスメソッドと同様に、継承され、オーバーライドできる。
が、定数はインスタンスメソッドのように動的ルックアップが行われない。
(動的ルックアップというのは、同名の関数が定義されている場合、一番近くで定義されたものを動的に採用するというもの)
優先されるのは、定数が使われている場所(メソッド)と同じスコープである。
その後、継承階層で探索が行われるが、これは抽象クラスにおける抽象メソッドの呼び出しのようなものになる。
# -*- coding: utf-8 -*-
class Point
def initialize(x,y)
@x = x
@y = y
end
def to_s; "(#@x,#@y)"; end
ORIGIN = Point.new(0,0) #selfを省略
#テストメソッド(内部でgreetingメソッドとORIGIN定数を参照する)
def test
"#{greeting}-#{ORIGIN}"
end
def greeting; "Hello"; end
end
class Point3D < Point
def initialize(x,y,z)
super(x,y)
@z = z
end
def to_s; "(#@x,#@y,#@z)"; end
def greeting; "GoodAfternoon"; end #greetingをオーバーライド
end
puts Point::ORIGIN #=> (0,0)を表示
puts Point3D::ORIGIN #=> (0,0)を表示 *POINT3D::ORIGINはPointクラスのインスタンスである。
Point3D::ORIGIN = Point3D.new(0,0,0)
puts Point::ORIGIN #=> (0,0)を表示 定数は共有されない
puts Point3D::ORIGIN #=> (0,0,0)を表示 *POINT3D::ORIGINはPoint3Dクラスのインスタンスである。
p = Point.new(1,1)
q = Point3D.new(1,1,1)
puts p.test #=> Hello-(0,0)を表示
puts q.test #=> GoodAfternoon-(0,0)を表示
testメソッドの定義はPointクラスにあり、内部でインスタンスメソッドgreetingと定数ORIGINを参照する。
p.testではgreetingもORIGINも、testメソッドと同じスコープから探し出す。
q.testでは動的ルックアップによりPoint3Dのgreetingを採用し、GoodAfternoonを返すが、
定数は、その定数を使用しているメソッド(ここではtest)と同じ場所(Pointクラス)から優先して探し出す。
そこで採用されるのがPoint::ORIGINというわけだ。
以上が、Rubyの継承メカニズムにおける六つの要素の挙動である。
コメント
コメントの投稿
トラックバック
http://kamiyasu2.blog.fc2.com/tb.php/35-014e372e