モジュールとクラス(1)
2009/08/06
次の内容を持つファイル mod.rb がカレントディレクトリに存在します。
module Mod
def foo
"Foo" + bar
end
private
def bar
"Bar"
end
end
Mod モジュールを利用して、クラスメソッド foo を持つクラス Klass を定義しなさい。
クラス Klass の利用例は次の通りです。
puts Klass.foo
結果として、端末に FooBar と出力されます。
--
黒田努
解答と解説
モジュールをクラスメソッドとしてインクルードするにはどうするか、という問題です。
少し難しかったようですね。
まず、MTGさんは、「正解にたどり着けませんでした」と断りつつ、次のコードを送ってくれました。
require 'mod' class Klass include Mod end puts Klass.new.foo #puts Klass.foo # => undefined method `foo' for Klass:Class (NoMethodError)
普通に include メソッドでモジュールを取り込むとこうなりますね。
次のコードは river125 さんの答えです。
require 'mod'
class Klass
include Mod
def self.foo
k = Klass.new
k.foo
end
end
問題には「Mod モジュールを利用して」と書いてあるので、これでも間違いではないのですが、メソッド foo を呼ぶたびにインスタンスが作られるのは、避けたいところです。
大詰めさんは、正解にたどり着きました。
require 'mod' class Klass extend Mod end
RubyリファレンスのObjectによれば、extend メソッドは、「引数で指定したモジュールのインスタンスメソッドを self の特異メソッドとして追加」すると説明があります。
特異メソッドとは何でしょうか。
それは、インスタンスメソッドとの対比で使われる言葉です。別名「シングルトン・メソッド」とも呼ばれます。
あるオブジェクト obj が持つメソッドは、インスタンスメソッドと特異メソッドに分類されます。
インスタンスメソッドは、そのオブジェクトのクラス(あるいは、その先祖に当たるクラス)で定義され、そのクラスのインスタンス全体で共有されています。
それに対し、特異メソッドは、そのオブジェクトにおいて定義されます。
より正確に言うと、そのオブジェクトが所有している特異クラスにおいて定義されます。
要するに、一個のオブジェクトによって専有されるメソッドが特異メソッドです。
では、次のように書いたとき、何が何をしていることになるのでしょう。
class Klass extend Mod end
メソッド extend の実行主体 self は表面に現れていませんが、クラス定義の直下ですから、クラス Klass が self です。
Ruby 言語では、クラスもオブジェクトであることを思い出してください。
つまり、クラス Klass という「オブジェクト」の特異メソッドとして、モジュール Mod のインスタンスメソッドを追加しているのです。
実は、「クラスメソッド」とはクラスの特異メソッドのことなのです!
最後に、模範解答です。
require 'mod'
class Klass
class << self
include Mod
end
end
class << self ... end は、特異クラスを定義する時の書き方です。この中でメソッドを定義すると、それがクラス Klass のクラスメソッドになります。
extend を使った場合よりもコードが長くなりますが、Klass 独自のクラスメソッド baz を追加したいとき次のように書けるという利点があります。
require 'mod'
class Klass
class << self
include Mod
def baz
"Baz"
end
end
end
モジュールから取り入れたクラスメソッドと独自のクラスメソッドの関係が明確になるので、私はこの書き方が好きです。
また、プライベートメソッド bar をオーバーライドすることもできます。
require 'mod'
class Klass
class << self
include Mod
private
def bar
"BAR"
end
end
end
ちなみに、次のように書いても一応オーバーライドできるのですが、bar がパブリックなメソッドになってしまいます。
require 'mod'
class Klass
extend Mod
private
def self.bar
"BAR"
end
end
private は、以降のメソッド定義のスコープをプライベートにするメソッドですが、特異クラスにおけるメソッド定義には効果がありません。
