クラス(やモジュール)定義のイベントの発生時に自動で呼ばれるメソッドが、Rubyには組み込みで用意されています。例えば、「継承」というイベントをhookするメソッドはBasicObject#inherited(klass)
です。
inherited
を使って、「このクラスは継承して欲しくないので、継承したら例外を投げる」という実装をしてみます。
class NonInheritable def self.inherited(klass) super raise StandardError, "#{self} is not allowed to be inherited." end def method_nonInheritable puts "#{__method__} is called." end end class Foo < NonInheritable; end #=> StandardError: NonInheritable is not allowd to be inherited. # ・・・(省略)・・・
self.inherited
としてクラスメソッドをオーバーライドしているところに注意してください。inherited
の第一引数には、継承している側(つまり子クラス)のクラスオブジェクトが入ります。- 継承されている側(つまり親クラス)のクラスが欲しい場合には
self
を使います。
さて、確かに例外が投げられているのでうまくいっているようです。
しかし、フックはあくまで「〜が起きたときに、・・・する」だけです。上記の例で言えば、あくまで継承時に例外を投げているだけです。継承自体を止められているわけではないです。つまり、この例外をrescue
するなりして処理を継続すれば、その後にFoo
をnew
することもできるし、そのnew
したオブジェクトから親クラスであるNonInheritable
クラスのメソッドを呼び出すこともできます。
class NonInheritable def self.inherited(klass) super raise StandardError, "#{self} is not allowed to be inherited." end def method_nonInheritable puts "#{__method__} is called." end end begin class Foo < NonInheritable; end rescue => e puts e.message end #=> NonInheritable is not allowed to be inherited. Foo.ancestors #=> [Foo, NonInheritable, Object, Kernel, BasicObject] Foo.new.method_nonInheritable #=> method_nonInheritable is called.
inherited
のような、クラス(やモジュール)定義のイベントをフックするメソッドは、全部で7つあります。*1
inherited
のみBasicObject
クラスで定義されていて、他はKernel
モジュールで定義されているようです。
irb(main):069:0> RUBY_VERSION => "2.2.3" irb(main):070:0> Object.private_methods(false).grep /ed$/ => [:inherited] irb(main):071:0> Kernel.private_methods(false).grep /ed$/ # :protectedはフックとは関係ないメソッド。無視してください。。 => [:included, :extended, :prepended, :method_added, :method_removed, :method_undefined, :protected]
整理してみます
メソッド | フックポイント | 注意 |
---|---|---|
inherited(child) |
クラスが継承されたとき | 第一引数は、継承する側のクラス(つまり子クラス)。 |
extended(obj) |
モジュールがオブジェクトに取り込まれた(extend された)とき |
第一引数は、extend する側のオブジェクト。 |
included(mod) |
モジュールがinclude されたとき |
第一引数は、include する側のモジュール。 |
prepended(mod) |
モジュールがprepend されたとき |
第一引数は、prepend する側のモジュール。Ruby2.0で導入。 |
method_added(name) |
クラスやモジュールでメソッドが定義されたとき | 第一引数は、追加されたメソッド名のシンボル。 |
method_removed(name) |
クラスやモジュールでメソッドが削除(remove_method )されたとき |
第一引数は、削除されたメソッド名のシンボル。 |
method_undefined(name) |
クラスやモジュールでメソッドが未定義に(undef_method )されたとき |
第一引数は、未定義にされたメソッド名のシンボル。 |
全部使ってみます
# フック時に表示するメッセージのテンプレート MESSAGE = "%09s has %09s %-s" # クラスにフックを定義 class Parent def self.inherited(klass); puts MESSAGE % [self, __method__, klass] end # クラスが継承されたとき end # モジュールにフックを定義 module Mod def self.extended(obj); puts MESSAGE % [self, __method__, obj] end # モジュールがオブジェクトに取り込まれた(extend)されたとき def self.included(mod); puts MESSAGE % [self, __method__, mod] end # モジュールがincludeされたとき def self.prepended(mod); puts MESSAGE % [self, __method__, mod] end # モジュールがprependされたとき def self.method_added(name); puts MESSAGE % [self, __method__, name] end # メソッドが定義されたとき def self.method_removed(name); puts MESSAGE % [self, __method__, name] end # remove_methodによってメソッドが削除されたとき def self.method_undefined(name); puts MESSAGE % [self, __method__, name] end # undef_methodによってメソッドが未定義にされたとき end class Child < Parent def to_s; "child" end end # inherited 発動 child = Child.new child.extend(Mod) # extended 発動 module SubMod include Mod # included 発動 prepend Mod # prepended 発動 end module Mod def foo; end # method_added 発動 def bar; end # method_added 発動 remove_method :foo # method_removed 発動 undef_method :bar # method_undefined 発動 end
- このコードでは端折ってますが、フックを使う場合は基本的に
super
を呼び出すのが行儀いいです。
参考
- 『Effective Ruby』 p107 項目28「モジュール、クラスフックを使いこなそう」
- http://yukithm.blogspot.com/2014/04/ruby-inherited.html
*1:prependedがRuby2.0で新たに導入された例もあり、今後増える可能性は大いにあります。