LoginSignup
2
0

More than 5 years have passed since last update.

おもしろメタプログラミング/5章

Last updated at Posted at 2017-09-09

木曜日 : クラス定義

できるようになること

  • クラスマクロ
  • アラウンドエイリアス

カレントクラス

カレントクラスを考える前にカレントオブジェクトの概念についておさらいします
rubyにはカレントオブジェクトselfが常にある

selfのおさらい

レシーバを明示しないメソッドのレシーバはselfになる

selfは次の3つのタイミングで変化する

class
module
def

self # => main

class MyClass

  self  # => MyClass 

  def my_method
    self
  end

end

obj = MyClass.new
obj.my_method  # => obj

カレントオブジェクトと同様にカレントクラスも存在する

カレントクラスはメソッド定義を支配しています
defによるメソッド定義はカレントクラスに対して行われる
カレントクラスはclassキーワードで切り替わる

class_eval

instance_evalのclass版、class_evalも存在する

instance_evalはカレントオブジェクトをレシーバにしてブロックを実行する

class MyClass
  def my_method
    @v = 1
  end
end

@v = 2

obj = MyClass.new
obj.my_method

obj.instance_eval do
  self  # => obj
  @v  # => ?
end

同じような感じで、
class_evalはカレントクラスをレシーバにしてブロックを実行する
-> クラスを再オープンしてメソッドを定義するのに使われる

module MyClass
  extend MyModule
  def self.included(klass)
    klass.class_eval do
      def self.my_method # <= クラスメソッドを追加している
        # code...
      end
    end
    # code...
  end
end

上のコードでは、klassに対してclass_evalでクラスを再オープンすることで
klassにさらにメソッドを追加している

classキーワードで開けない、つまりクラスの名前が分からないとき(引数で渡ってくるときなど)に
メソッドを追加するのに使われる

特異メソッドの紹介

オブジェクトにクラスにはない独自のメソッドを追加することができる。
これを特異メソッドと呼ぶ。

使い方

str = "HELLO"

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

str.title?  # => true
str.singleton_methods  # => [:title?]

実は、クラスメソッドはクラスの特異メソッドにすぎない

class MyClass
  def self.my_singleton  # class定義の中では self == MyClass
    puts "this is a singleton method!"
  end
end

特異クラス

特異メソッドがどこに属しているか不思議じゃないですか?
- メソッド探索的にはインスタンスのクラスにメソッドが追加されてなくちゃおかしい
- でもクラスに追加されてたら他のインスタンスからも呼べてしまう…

実は、
すべてのオブジェクトは独自のクラス、特異クラスを持っている

class MyClass; end

obj = MyClass.new
obj.class  # => MyClass
obj.singleton_class  # => #<Class:#<MyClass:0x007ff227894650>>

特異クラスは#singleton_classで調べられる

メソッド探索の順番は
objの特異クラス -> MyClass -> ...
となる

class定義の中での特異クラスの呼び方

class MyClass
  class << self  # selfの特異クラスを開く == カレントクラスをselfの特異クラスにする
    def my_singleton_method
      puts "資料作るの大変…"
    end
  end
end

class << hogeで特異クラスを開くことができる
上のコードはクラスメソッドを定義しているのと同じ
(クラスメソッド == クラスの特異メソッド)

クラスマクロ

attr_accessorattr_readerなどクラス定義の中でキーワードのように呼べるメソッドがある
このようなメソッドはクラスマクロと呼ばれている

でも、
クラスマクロは、単なるクラスメソッド。

class MyClass
  attr_accessor :id, :name  # self.attr_accessorを呼んでるだけ

  def initialize(id, name)
    @id = id
    @name = name
  end
end

だからクラスメソッドを定義すれば簡単にクラスマクロを作ることができる

includeとextendについて

特異クラスを開いているクラスマクロの例の一つが、extend

include <- includeしたクラスにインスタンスメソッドを拡張する
extend <- extendしたクラスにクラスメソッドを拡張する

extendを使わずにクラスメソッドを拡張してみる

module MyModule
  def my_method; 'hello'; end
end

obj = Object.new

class << obj
  include MyModule
end

obj.my_method  # => 'hello'

これは下のコードと同じことをしている

module MyModule
  def my_method; 'hello'; end
end

obj = Object.new
obj.extend MyModule
obj.my_method # => 'hello'

アラウンドエイリアス

クラスマクロModule#alias_methodを使ってメソッドをエイリアスをする(別名をつける)ことができる

class MyClass
  def my_method; 'hello'; end
  alias_method :m, :my_method
end

obj = MyClass.new
obj.my_method  # => 'hello'
obj.m  # => 'hello'

上のコードではmy_methodに別名mを与えている
古い名前でも呼ぶことができる

String#sizeはString#lengthのエイリアスらしい(なんで必要だったんだろう)

エイリアスを有効に使うと以下のようにメソッドを名前を変えずに再定義することができる

class String
  alias_method :real_length, :length
  # def real_lenght
  #   length
  # end

  def length 
    real_length > 5 ? 'long' : 'short'
  end
  # lengthをオーバライド
end

"hello hello hello".length  # => "long"
"hello hello hello".real_length  # => 17

上のコードでは
alias_methodでlengthのエイリアスreal_lengthを作成し、
def lengthを開いてオーバライドすることで
lengthを再定義している

このような手法をアラウンドエイリアスと呼ぶ
アラウンドエイリアスの手順をまとめる
1. メソッドにエイリアスをつける
2. メソッドを再定義する
3. 新しいメソッドから古いメソッドを呼ぶ

ちょっとした演習

問題1

クラスマクロ'alias_method'を実装してみて正しく動くことを確認する

問題2

(メタプロの本から引用)
Fixnum#+を再定義して足し算の結果に必ず+1されるようにする
例えば

1 + 1   # => 3
3 + 4   # => 8

という感じ

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0