メタプログラミングRuby第2版:5章:クラス

スポンサーリンク

メタプログラミングRubyとは?

出版社である O’Reilly Japanのサイトからの引用です。

本書はRubyを使ったメタプログラミングについて解説する書籍です。メタプログラミングとは、プログラミングコードを記述するコードを記述することを意味します。前半では、メタプログラミングの背景にある基本的な考えを紹介しながら、動的ディスパッチ、ゴーストメソッド、フラットスコープといったさまざまな「魔術」を紹介します。後半では、ケーススタディとしてRailsを使ったメタプログラミングの実例を紹介します。今回の改訂では、Ruby 2とRails 4に対応し、ほぼすべての内容を刷新。Rubyを使ったメタプログラミングの魔術をマスターし、自由自在にプログラミングをしたい開発者必携の一冊です。

引用:https://www.oreilly.co.jp/books/9784873117430/

5章:クラスを写経し終えたので内容を振り返ってまとめてみました。

クラス定義の説明

カレントクラス

Rubyのプログラムは常にカレントオブジェクトselfを持っている。
同様に、常にカレントクラスも持っている。
メソッドは定義されるとカレントクラスのインスタンスメソッドになる。

  • トップレベルではカレントクラスはObjectになり、Objectのインスタンスメソッドになる
  • classキーワードでクラスをオープンするとそれがカレントクラスになる
  • メソッドの中では、カレントオブジェクトのクラスがカレントクラスになる

オープンしたいクラスの名前がわからないときはModule#class_evalを用いる

  • class_evalはカレントクラスをレシーバにしてブロックを実行する
  • クラスを再オープンしてメソッドを定義するのに使われる
  • ブロックなのでフラットスコープを持っている
def add_method_to(a_class)
  a_class.class_eval do
    def m
      'Hello!'
    end
  end
end

add_method_to(String)
'abc'.m # => "Hello!"

クラスインスタンス変数

  • すべてのインスタンス変数はカレントオブジェクトselfに属す。
  • クラス定義の中ではselfはクラスになる(クラスインスタンス変数)
  • クラスのインスタンス変数とクラスのオブジェクトのインスタンス変数は別物なので注意
class MyClass
  @my_var = 1

  def self.read; [self, @my_var]; end

  def write; @my_var = 2; end

  def read; [self, @my_var]; end
end
MyClass.read # => [MyClass, 1]

obj = MyClass.new
obj.read # => [#<MyClass:0x00007f8254a9fd20>, nil]
obj.write
obj.read # => [#<MyClass:0x00007f8254a9fd20 @my_var=2>, 2]

MyClass.read # => [MyClass, 1]

特異メソッド

Rubyでは特定のオブジェクトにメソッドを追加でき、これを特異メソッドと呼ぶ。
クラスメソッドも特異メソッドの一つ。

str = 'just a regular string'

def str.title?
  self.upcase == self
end

str.title? # => false
str.methods.grep(/title/) # => [:title?]
str.singleton_methods # => [:title?]

Rubyのような動的言語ではそのオブジェクトがDuckクラスか?は重要ではなく、そのオブジェクトから指定したメソッドが呼び出せるかどうか?が重要であり、これをダックタイピングという。

アクセサメソッドの代わりに用いるModule#attr_*族のようなクラスメソッドをクラスマクロと呼ぶ。
以下はクラスメソッドdeprecateというクラスマクロを用いてメソッド名を刷新する例。

class Book
  def lend_to(user)
    puts "Lending to #{user}"
  end

  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      warn "Warning: #{old_method}() is deprecated. Use #{new_method}()."
      send(new_method, *args, &block)
    end
  end

  deprecate(:LEND_TO_USER, :lend_to)
end

b = Book.new
b.LEND_TO_USER('Bill')
# =>
# Warning: LEND_TO_USER() is deprecated. Use lend_to().
# Lending to Bill

特異クラス

特異クラスの概要

  • オブジェクトは通常見ているクラスとは別に特別なクラスを持っている
  • 特異クラス(シングルトンクラスとも)という
  • オブジェクトをレシーバにしたsingleton_classメソッドで確認できる
  • 特異メソッドは特異クラスのインスタンスメソッドとして定義される
  • 特異クラスはそのオブジェクトの通常クラスの手前に差し込まれる
class C
  def a_method
    'C#a_method()'
  end
end

class D < C; end

obj = D.new
obj.a_method # => "C#a_method()"

# ①objの特異クラスを返す singleton_classメソッド
obj.singleton_class # => #<Class:#>

# ②特異メソッドを定義すると、特異メソッドが特異クラスに住んでいる事がわかる
obj.singleton_class.instance_methods(false) # => []
D.instance_methods(false) # => []

class << obj
  def a_singleton_method
    'obj#a_singleton_method()'
  end
end

obj.singleton_class.instance_methods(false) # => [:a_singleton_method]
D.instance_methods(false) # => []

# ③特異クラスはそのオブジェクトの通常クラスの手前に差し込まれる
obj.singleton_class.superclass # => D
obj.singleton_class.superclass.suerclass # => C

特異クラスの使い方

実際には、特異クラスはあるクラスのみに限定したアトリビュートを付与したいときに用いる。
RailsのModelでよくやるやつ。

class MyClass
  class << self
    attr_accessor :a
  end
end

MyClass.a = 'It works!'
MyClass.a # => "It works!"

class OtherClass; end
OtherClass.a = 'It works!' # => NoMethodError: undefined method `a' for OtherClass:Class

クラス拡張とObject#extend

モジュールを特異クラスでインクルードすると、モジュールのメソッドは特異クラスのインスタンスメソッドになる。

つまり、モジュールのメソッドをクラスメソッドに出来る。
これをクラス拡張という。

module MyModule
  def my_method; 'hello' end
end

class MyClass
  class << self
    include MyModule
  end
end

MyClass.my_method # => "hello"
MyClass.singleton_class # => #<Class:MyClass>
MyClass.singleton_methods.grep(/my_method/) # => [:my_method]

Object#extendを使えば省略して書ける

class MyClass
  extend MyModule
end

メソッドラッパー

アラウンドエイリアス

新しいメソッドで古いメソッドをラップすることを指し、以下手順で行う。

  1. メソッドにエイリアスをつける
  2. メソッドを再定義する
  3. 新しいメソッドから古いメソッドを呼び出す

新しいメソッドの中で、古いメソッドの戻り値が必要な時に使う。
デメリット:一種のモンキーパッチなので既存メソッドの利用に影響を及ぼしかねない

class String
  alias_method :real_length, :length

  def length
    real_length > 5 ? 'long' : 'short'
  end
end

'War and Peace'.length # => long
'War and Peace'.real_length # => 13

Prependラッパー

継承チェーンでインクルーダーの下にモジュールが挿入されるprependを用いることでインクルーダーのメソッドのみをオーバーライドできる

module ExplicitString
  def length
    super > 5 ? 'long' : 'short'
  end
end

String.class_eval do
  prepend ExplicitString
end

'War and Peace'.length # => long
          
    

スポンサーリンク

  
Scroll Up