モジュールとクラス(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
は、以降のメソッド定義のスコープをプライベートにするメソッドですが、特異クラスにおけるメソッド定義には効果がありません。