パーフェクトRubyお勉強フェスティバル 第6章 「Rubyのクラスオブジェクト」

oftonkingdom.hatenablog.com



前回の記事 oftonkingdom.hatenablog.com

前回(5章)の続きを書こうかと思ったのですが間違えて下書きを消してしまいました…

5章の残り,内容がファイルやディレクトリの操作,ThreadやFiber,プロセスについてであり,このあたりは初めて読む人は無理に読まなくてもいいよみたいな意見も見たりしたので,がっつり使う機会が来たら戻ってこようかとも思います.

というわけで「6章 Rubyのクラスオブジェクト」です.メタプログラミングの内容に入っていきます.

クラスもオブジェクト??

今までオブジェクト指向言語Javaなど)書く中でクラスはクラス宣言することでしか書いたことがないが,クラスですらClassクラスのオブジェクトなのである.

クラスの継承ツリーは次のようになっている.

p Class.ancestors

出力

[Class, Module, Object, Kernel, BasicObject]

Class.newで作成したクラスオブジェクトを定数に代入することで,クラスを定義できる.

無名クラス

Class.newで作成したクラスオブジェクトを変数に代入すると「無名クラス」となる.

これ,どんなときに使えるのかよくわからなかったのでちょっとググりました.この記事だと,引数内でクラスを定義するときに使っていますね.

secret-garden.hatenablog.com

JavaScriptで無名関数が便利なときって,そこでしか行わない処理(使い捨てみたいな)を書くときに,離れたところに書かなくてもいいから便利みたいなイメージがあるのですが,それと近い?

class定義式とClass.newの違い

class定義式だとclass定義式外のスコープを参照できないが,Class.newだとブロックで定義するのでブロック外のスコープも参照できる.

これが動的なプログラミングたるゆえん,なのでしょうか.それまでの処理の流れを活かした上でクラスを定義したい場合などはClass.newに軍配が挙がるということになるのでしょうか.

また,class定義式内,Class.newに渡すブロック内における,「self」が指すものはいずれも以下の特徴を持つ * メソッド定義内のselfはクラスのインスタンスを指す * メソッド定義以外でのselfはクラスオブジェクトを指す

なお,Class.newに渡されるブロックのブロック引数は「クラスオブジェクト」を指す.

ややこしい「インスタンス変数」「クラスインスタンス変数」「クラス変数」

ややこしいので1つのコードにまとめてみました.

「クラスインスタンス変数」はクラスオブジェクトのインスタンス変数ということなので,上述の「self」が「クラスオブジェクト」を指すような場所で「インスタンス変数」を定義すればよい.

class Ofton
  #「クラスインスタンス変数」定義パターン1:インスタンスメソッド定義外で定義
  @class_instance_val1 = :class_instance_val1
  
  #これは「クラス変数」
  @@class_val = :class_val
  
  #クラスメソッド「count_ofton」
  def self.count_ofton
    #「クラスインスタンス変数」定義パターン2:クラスメソッド定義外で定義
    @class_instance_val2 = :class_instance_val2
    
    #class_instance_val1を参照もできる
    @class_instance_val1 #=> :class_instance_val1
  end
  
  #インスタンスメソッド「sleep」
  def sleep
    #これは「インスタンス変数」
    @instance_val = :instance_val
    
    #インスタンスメソッドにはクラスインスタンス変数は「見せられないよ!!!!!」であるため,
    #以下については「class_instance_val1」という名前の「インスタンス変数」を探そうとする.
    #そんなものはないためnilが返る.
    @class_instance_val1 #=> nil
    
    #クラス変数は見せられるよ!!!!!!!!
    @@class_val #=> :class_val
  end
end

#Oftonクラスを継承した「UmoButon」クラスから親クラスの変数を参照する
class UmoButon < Ofton
  #クラスインスタンス変数は子クラスには「見せられないよ!!!!!」であるため,
  #インスタンスメソッドからのクラスインスタンス変数の参照と同様の理由でnilが返る.
  @class_instance_val1 #=> nil
  
  #クラス変数は見せられるよ!!!!!!!!
  @@class_val #=> :class_val
end

結局,クラス変数とクラスインスタンス変数は何が違うのかというと…クラス変数は継承したクラスからも見える特徴があるため,継承したクラスから見せたくないような場合(特定のクラスに限定した処理を行いたい場合など)にクラスインスタンス変数を使う,ということで納得しました.

特異クラス,特異メソッド

特異メソッドは特定のオブジェクトにのみ定義されるメソッドなので,直接のクラスには定義されていない.オブジェクトに対する「特異クラス」と呼ばれる場所で定義されるのである.

なお,クラスメソッドも,Classオブジェクトの特異クラスに定義されているのである.

class Ofton
end

ofton_obj = Ofton.new

#特異メソッドを定義
def ofton_obj.fukafuka
  'ふかふか'
end

#Oftonクラスに上記の特異メソッドは定義されていない
p ofton_obj.class.method_defined? :fukafuka #=> false

#Oftonクラスの特異クラスに上記の特異メソッドは定義されている
p ofton_obj.singleton_class.method_defined? :fukafuka #=> true


#特異クラスの親クラスがOftonクラスになっている
p ofton_obj.singleton_class.superclass #=> Ofton

特異クラスの使い道とは?シングルトンオブジェクト

シングルトンオブジェクト,いまいち何がうれしいのかあまりよくわかっていなかったです.デザインパターンの一種なんですよね.

teratail.com

Module#prepend

そもそもモジュールのインクルードを行う「include」もModule#includeというメソッドなんですね.

prependとは「先頭に追加する」という意味.継承ツリーでは,Module#prependの呼び出し元であるクラス/モジュールよりも手前に,Module#prependに引数で渡したモジュールをラップしたクラスが追加される.つまり,同名のメソッドがあった場合,Module#prependで定義されたメソッドが呼び出される.以下の例では,OftonModule#helloはOfton#helloをオーバーライドしており,OftonModule#helloが呼び出される.

module OftonModule
  def hello
    "Hello from OftonModule"
  end
end

class Ofton
  prepend OftonModule
  
  def hello
    "Hello from Ofton"
  end
end

obj = Ofton.new
puts obj.hello #=> "Hello from OftonModule"

実用例としてRuby on Railsのbefore_actionが例としてあげられています.