ようへいの日々精進XP

よかろうもん

俺は Ruby について 1bit も理解していなかったので写経する 〜 オブジェクト指向編 (2) 〜

これは

  • Ruby Gold を受験するにあたって学んでいること等を写経したり, メモったりしています
  • Ruby 技術者認定試験合格教本」を主に参考にしており, 今回は第四章「オブジェクト指向」を写経していきます
  • オブジェクト指向、苦手分野だし, 学ばないといけない事が多いので 2 回くらいに分けて記載予定です
  • 実行例では主に pry を利用していますが, 例外が発生した際の実行過程出力(from から始まる行)は省略しています...すいません
[12] pry(main)> Baz2.new.private_method1
NoMethodError: private method `private_method1' called for #<Baz2:0x005606f46e2778>
from (pry):29:in `__pry__'

クラスメソッド

はじめに

  • クラスメソッドとは, クラスオブジェクトをレシーバとするメソッド
  • インスタンス生成時の new メソッドはクラスメソッド
  • Ruby のクラスメソッドは, クラスに対する特異クラスのメソッドとして定義する

Class クラス

  • クラスオブジェクトと呼んできたものは, Class クラスのオブジェクト
  • Class クラスはクラスを定義出来るクラス

以下のようにクラスに対して, class メソッドを実行することで Class クラスのオブジェクトとして取得出来る.

foo_class = Foo.class

Class クラスをインスタンス化してクラスを定義することが出来る. クラスは Class クラスのインスタンスなので, 通常のインスタンスと同様に Class クラスの new メソッドでクラスを定義することが出来る.

SomeClass = Class.new
SomeClass.new

Class の new メソッドで, スーパークラスやメソッドを定義することも出来る. new メソッドの引数でスーパークラスを, ブロックでメソッドを定義する.

クラスメソッドの定義

以下, クラスメソッド定義例. Class クラスを再オープンしてメソッドを定義している.

class Class
  def c_method
    1
  end
end

p Foo.c_method
p String.c_method
p Object.c_method

以下, 実行例.

# Foo クラスを定義
[1] pry(main)> class Foo;end
=> nil

# Class クラスを再オープンしてクラスメソッド c_method を定義
[2] pry(main)> class Class
[2] pry(main)*   def c_method  
[2] pry(main)*     1    
[2] pry(main)*   end    
[2] pry(main)* end  
=> :c_method
[3] pry(main)> p Foo.c_method
1
=> 1
[4] pry(main)> p String.c_method
1
=> 1する
[5] pry(main)> p Object.c_method
1
=> 1
  • メソッド探索順序のルール一つ抽象度を上げて継承の方向へは Class クラスでも同じ
  • Class クラスで見つからない場合, Module クラスを探す
  • new メソッドは Class メソッドに定義されているので Module インスタンスからは実行出来ない
  • クラスオブジェクトは継承関係を保持している(クラスメソッドも継承される)

以下, クラスメソッドの定義例.

# その 2
class Foo
  def Foo.c_method2; 2; end
end

# その 3
class Foo
  def self.c_method3; 3; end
end

# その 4(特異クラスの再オープンを利用)
class Foo
  class << self
    def self.c_method4; 4; end
    def self.c_method5; 5; end
  end
end

以下は正常に実行出来るか??

module M4
  def method1; 1; end
end

class C4
  include M4
  extend M4
end

C4.method1
c4 = C4.new
c4.method1

以下, 実行例.

[6] pry(main)> module M4
[6] pry(main)*   def method1; 1; end  
[6] pry(main)* end  
=> :method1
[7] pry(main)> 
[8] pry(main)> class C4
[8] pry(main)*   include M4  
[8] pry(main)*   extend M4  
[8] pry(main)* end  
=> C4
[10] pry(main)> C4.method1
=> 1
[11] pry(main)> c4 = C4.new
=> #<C4:0x0055eb38b39038>
[12] pry(main)> C4.method1
=> 1

extend は特異クラスの継承先に無名クラスを挿入するので, クラスに対して実行するとモジュールのメソッドがクラスメッドとして動作する.

メソッドの可視性と組み込み関数

メソッドの可視性

  • public が指定されたメソッドは, どのインスタンスからも実行可能
  • protected は自分自身, 又はサブクラスのインスタンスから実行可能
  • private はレシーバ付けた呼び出しは出来ない
class Baz1
  def public_method1; 1; end
  public
  def public_method2; 2; end
  protected
  def protected_method1; 1; end
  def protected_method2; 1; end
  private
  def private_method1; 1; end
end

以下, 実行例.

[7] pry(main)> class Baz1
[7] pry(main)*   def public_method1; 1; end  
[7] pry(main)*   public  
[7] pry(main)*   def public_method2; 2; end  
[7] pry(main)*   protected  
[7] pry(main)*   def protected_method1; 1; end  
[7] pry(main)*   def protected_method2; 1; end  
[7] pry(main)*   private  
[7] pry(main)*   def private_method1; 1; end  
[7] pry(main)* end  
=> :private_method2
[8] pry(main)> Baz1.new.public_method1
=> 1
[9] pry(main)> Baz1.new.public_method2
=> 2
[10] pry(main)> Baz1.new.protected_method1
NoMethodError: protected method `protected_method1' called for #<Baz1:0x005642a0501098>
[11] pry(main)> Baz1.new.protected_method2
NoMethodError: protected method `protected_method2' called for #<Baz1:0x005642a068f478>
[12] pry(main)> Baz1.new.private_method1
NoMethodError: private method `private_method1' called for #<Baz1:0x005642a0cfefe8>

以下のように, メソッド名をシンボルで指定することも可能.

class Baz1
  def public_method1; 1; end
  def public_method2; 2; end
  def protected_method1; 1; end
  def protected_method2; 1; end
  def private_method1; 1; end
  
  public :public_method1, :public_method2
  protected :protected_method1, :protected_method2
  private :private_method1
end

private はレシーバを省略して呼び出す必要がある.

class Baz2
  def public_method1
    private_method1
  end
  def public_method2
    self.private_method1
  end
  private
  def private_method1; end
end

以下, 実行例.

[1] pry(main)> class Baz2
[1] pry(main)*   def public_method1  
[1] pry(main)*     private_method1    
[1] pry(main)*   end    
[1] pry(main)*   def public_method2  
[1] pry(main)*     self.private_method1    
[1] pry(main)*   end    
[1] pry(main)*   private  
[1] pry(main)*   def private_method1; end  
[1] pry(main)* end  
=> :private_method1
[3] pry(main)> Baz2.new.public_method1
=> nil
[4] pry(main)> Baz2.new.public_method2
NoMethodError: private method `private_method1' called for #<Baz2:0x005606f446f018>

メソッドの可視性はクラスに結びついているので, サブクラスで自由に変更出来る.

[11] pry(main)> class Baz2Ext < Baz2
[11] pry(main)*   public :private_method1  
[11] pry(main)* end  
=> Baz2Ext
[12] pry(main)> Baz2.new.private_method1
NoMethodError: private method `private_method1' called for #<Baz2:0x005606f46e2778>
[13] pry(main)> Baz2Ext.new.private_method1
=> nil

以下のように継承したクラスからは private メソッドを呼び出すことが出来る.

[1] pry(main)> class Baz
[1] pry(main)*   private
[1] pry(main)*   def m; puts 'ok'; end
[1] pry(main)* end  
=> :m
[2] pry(main)> class Baz3Ext < Baz
[2] pry(main)*   def m
[2] pry(main)*     super
[2] pry(main)*   end  
[2] pry(main)*   def m2
[2] pry(main)*     m
[2] pry(main)*   end  
[2] pry(main)* end  
=> :m2
[3] pry(main)> Baz3Ext.new.m
ok
=> nil
[4] pry(main)> Baz3Ext.new.m2
ok
=> nil

Kernel モジュールと関数

  • put や proc は Kernel モジュールのメソッド
[1] pry(main)> self.class
=> Object
  • クラス定義の外側(トップレベル)で self を参照すると, Object インスタンスが返る
  • クラス定義の外側でレシーバを省略して実行すると, Object クラスで定義されたメソッドが呼ばれる
  • Object クラスは Kernel モジュールを include しているので, 最終的には Kernel モジュールで定義されたメソッドが呼び出される
  • Object クラスは全クラスのスーパークラスなので, Kernel モジュールで定義されたメソッドはプログラム中のどこでも呼び出すことが出来る
  • Kernel モジュールの多くのメソッドは private 指定されているので, レシーバを指定して呼び出すことは出来ない
[2] pry(main)> self.p
NoMethodError: private method `p' called for main:Object

以下のように Kernel モジュールや Object クラスに private メソッドを追加することが出来る.

module Kernel
  private
  def my_func; end
end

class Object
  private
  def my_func2; end
end

以下, 実行例.

[3] pry(main)> module Kernel
[3] pry(main)*   private  
[3] pry(main)*   def my_func; end  
[3] pry(main)* end  
=> :my_func
[4] pry(main)> my_func
=> nil
[5] pry(main)> self.my_func
NoMethodError: private method `my_func' called for main:Object
[6] pry(main)> class Object
[6] pry(main)*   private  
[6] pry(main)*   def my_func2; end  
[6] pry(main)* end  
=> :my_func2
[7] pry(main)> my_func2
=> nil
[8] pry(main)> self.my_func2
NoMethodError: private method `my_func2' called for main:Object

特に理由がない限りは Kernel モジュールではなく, Object クラスに private メソッドを追加すること.

変数と定数

ローカル変数とグローバル変数

[1] pry(main)> v1 = 1
=> 1
[2] pry(main)> class Qux1
[2] pry(main)*   p v1
[2] pry(main)* end  
NameError: undefined local variable or method `v1' for Qux1:Class
  • 英小文字又はアンダースコアで始まる変数はローカル変数
  • トップレベルで定義された v1 を, Qux1 クラス内部から参照出来ない
[2] pry(main)> class Qux2
[2] pry(main)*   v2 = 2  
[2] pry(main)*   def method1; v1; end  
[2] pry(main)*   def method2; v2; end  
[2] pry(main)* end  
=> :method2
[3] pry(main)> Qux2.new.method1
NameError: undefined local variable or method `v1' for #<Qux2:0x00563c1de7cd68>
[4] pry(main)> Qux2.new.method2
NameError: undefined local variable or method `v2' for #<Qux2:0x00563c1deb75f8>
  • メソッドからは, トップレベルに加えてクラス内部, メソッドの外部で定義されたローカル変数も参照出来ない
  • クラス定義と内部のメソッドはそれぞれ独立したスコープを持ち, 相互参照は出来ない
[9] pry(main)> $v1 = 1
=> 1
[10] pry(main)> class Qux2
[10] pry(main)*   $v2 = 2
[10] pry(main)*   def method1; $v1; end  
[10] pry(main)*   def method2; $v2; end  
[10] pry(main)* end  
=> :method2
[11] pry(main)> Qux2.new.method1
=> 1
[12] pry(main)> Qux2.new.method2
=> 2

インスタンス変数

  • 変数の頭に @ を付けて初期化する
  • 初期化していない場合には nil を返す
[1] pry(main)> @v1 = 1
=> 1
[2] pry(main)> class Qux3
[2] pry(main)*   @v2 = 2  
[2] pry(main)*   def method1; @v1; end  
[2] pry(main)*   def method2; @v2; end  
[2] pry(main)* end  
=> :method2
[3] pry(main)> Qux3.new.method1
=> nil
[4] pry(main)> Qux3.new.method1
=> nil

インスタンス変数をインスタンス内に格納し, 外部からアクセスするには, インスタンスメソッド内部で初期化し, アクセサメソッドを定義する必要がある.

[5] pry(main)> class Qux4
[5] pry(main)*   def v3  
[5] pry(main)*     return @v3    
[5] pry(main)*   end    
[5] pry(main)*   def v3=(value)  
[5] pry(main)*     @v3 = value    
[5] pry(main)*   end    
[5] pry(main)*   def method1; @v3; end  
[5] pry(main)* end  
=> :method1
[6] pry(main)> 
[7] pry(main)> qux4 = Qux4.new
=> #<Qux4:0x0055e9192b3588>
[8] pry(main)> qux4.v3 = 3
=> 3
[9] pry(main)> p qux4.v3
3
=> 3
[10] pry(main)> p qux4.method1
3
=> 3

以下は, アクセサの生成を行うクラスメソッドが用意されている.

メソッド 詳細 備考
attr_reader getter メソッドを生成する
attr_writer setter メソッドを生成する
attr_accessor getter と setter メソッドを生成する
attr getter メソッドを生成する, 第二引数に true を指定すると setter メソッドも生成される Ruby 1.9 以降では非推奨

以下は attr_accessor の利用例.

[1] pry(main)> class Qux4
[1] pry(main)*   attr_accessor :v3  
[1] pry(main)*   def method1; @v3; end  
[1] pry(main)* end  
=> :method1
[2] pry(main)> qux4 = Qux4.new
=> #<Qux4:0x00555a68bf9a60>
[3] pry(main)> qux4.v3 = 3
=> 3
[4] pry(main)> p qux4.v3
3
=> 3
[5] pry(main)> p qux4.method1
3
=> 3

インスタンス変数は継承されないが, メソッドは継承されるので, サブクラスからアクセッサを経由してスーパークラスインスタンス変数にアクセスすることが可能.

[7] pry(main)> qux4Ext = Qux4Ext.new
=> #<Qux4Ext:0x00555a698f07a0>
[8] pry(main)> qux4Ext.v3 = 4
=> 4
[9] pry(main)> qux4Ext.v3
=> 4

クラス変数

  • @@ から始める
  • クラス変数は, インスタンス間で共有される
  • 自分自身のクラスとサブクラスでも共有される
[10] pry(main)> class Qux5
[10] pry(main)*   @@v1 = 1  
[10] pry(main)*   def v1; @@v1; end  
[10] pry(main)*   def v1=(value); @@v1 = value; end  
[10] pry(main)* end  
=> :v1=
[11] pry(main)> 
[12] pry(main)> class Qux5Ext < Qux5
[12] pry(main)* end  
=> nil
[13] pry(main)> 
[14] pry(main)> qux5 = Qux5.new
=> #<Qux5:0x00555a69b4ab90>
[15] pry(main)> p qux5.v1
1
=> 1
[16] pry(main)> qux5Ext = Qux5Ext.new
=> #<Qux5Ext:0x00555a69bad718>
[17] pry(main)> p qux5Ext.v1
1
=> 1

クラス変数はサブクラスやインスタンスからも共有されるので, あるインスタンスから値を更新すると, それあ全てのクラスのクラス変数に影響する.

# サブクラスのインスタンスから書き換える
[18] pry(main)> qux5Ext.v1 = 10
=> 10
[19] pry(main)> qux5.v1
=> 10
# 別のインスタンスから書き換える
[20] pry(main)> Qux5.new.v1 = 100
=> 100
[21] pry(main)> qux5.v1
=> 100

以下のように, 同名のクラス変数をサブクラスで宣言すると, 実際には代入となってスーパークラスのクラス変数を書き換えてしまう.

[22] pry(main)> class Qux6
[22] pry(main)*   @@v1 = 1  
[22] pry(main)*   def v1; @@v1; end  
[22] pry(main)* end  
=> :v1
[23] pry(main)> 
[24] pry(main)> class Qux6Ext < Qux6
[24] pry(main)*   @@v1 = 2  
[24] pry(main)* end  
=> 2
[25] pry(main)> 
[26] pry(main)> Qux6.new.v1
=> 2

ネストしたスコープの定義

  • 定数は大文字から始める名前指定して初期化する
  • 再代入しようとすると警告が発生する
[1] pry(main)> A = 1
=> 1
[2] pry(main)> p A
1
=> 1
[3] pry(main)> A = 2
(pry):3: warning: already initialized constant A
(pry):1: warning: previous definition of A was here
=> 2
[4] pry(main)> p A
2
=> 2
  • 定数はメソッドの中で定義することが出来ない
  • メソッドは複数回の実行が前提なので定数の初期化, 値の更新を許していない
[5] pry(main)> def func
[5] pry(main)*   B = 1
[5] pry(main)* end  
SyntaxError: (eval):3: dynamic constant assignment
  B = 1
     ^

モジュールオブジェクトやクラスオブジェクトが格納された定数に対しては :: という演算子を指定してネストを指定出来る.

[5] pry(main)> module B; end
=> nil
[6] pry(main)> B::A = 1
=> 1
[7] pry(main)> B::A::A = 1
TypeError: 1 is not a class/module

ネストした定数は, 以下のようにクラス式やモジュール式をネストして指定することでも生成可能.

module M
  A = 1
  class B
    A = 2
  end
  class C
  end
end

ネストしたスコープの参照

以下のようにネストした定数を参照する.

[8] pry(main)> module M
[8] pry(main)*   A = 1  
[8] pry(main)*   class B  
[8] pry(main)*     A = 2    
[8] pry(main)*   end    
[8] pry(main)*   class C  
[8] pry(main)*   end    
[8] pry(main)* end  
=> nil
[9] pry(main)> M::A
=> 1
[10] pry(main)> M::B::A
=> 2

モジュール M を再オープンして定数を参照する.

[11] pry(main)> module M
[11] pry(main)*   p A
[11] pry(main)*   p B::A
[11] pry(main)* end  
1
2
=> 2

外部から参照可能な定数を調べるには, Module クラスの constans メソッドを使用する.

[12] pry(main)> M.constants
=> [:A, :B, :C]
[13] pry(main)> M::B.constants
=> [:A]
[14] pry(main)> M::C.constants
=> []

ルートからの絶対値で指定する場合には, 以下のように先頭に :: を書くことでルートを指定する.

[15] pry(main)> module M
[15] pry(main)*   p ::M::A  
[15] pry(main)*   p ::M::B::A  
[15] pry(main)* end  
1
2
=> 2

指定した定数が見つからない場合, ネストの外側のクラスやモジュールの定数を内側から外側に向かって順に探索する.

[16] pry(main)> module M
[16] pry(main)*   class C  
[16] pry(main)*     p A    
[16] pry(main)*   end    
[16] pry(main)* end  
1
=> 1

クラス C の中で定数 A を参照すると, C の外側のモジュール M の中にある整数定数 A が参照される.

外側のモジュールにも存在しない場合, 現在のモジュールやクラスの継承チェーンを辿る為, 親クラスやモジュールで宣言された定数は, サブクラスや include したクラスでも参照出来る.

[17] pry(main)> class Foo
[17] pry(main)*   A = 1  
[17] pry(main)* end  
=> 1
[18] pry(main)> 
[19] pry(main)> module Bar
[19] pry(main)*   B = 2  
[19] pry(main)* end  
=> 2
[20] pry(main)> 
[21] pry(main)> class FooExt < Foo
[21] pry(main)*   include Bar  
[21] pry(main)*   p A  
[21] pry(main)*   p B  
[21] pry(main)* end  
1
2
=> 2

スーパークラスにも定数が見つからない場合, Ruby インタプリタは自分自身のクラスメソッド const_missing を呼び出す. const_missing のデフォルトで, 例外 NameError を発生する. このメソッドをサブクラスで上書きすることで, 定数が見つからない場合の動作を制御出来る.

[22] pry(main)> module M
[22] pry(main)*   def self.const_missing(id)  
[22] pry(main)*     "定数が見つからないよ"    
[22] pry(main)*   end    
[22] pry(main)* end  
=> :const_missing
[23] pry(main)> M::HOGE
=> "定数が見つからないよ"

以上

写経でした.