これは
- 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 は特異クラスの継承先に無名クラスを挿入するので, クラスに対して実行するとモジュールのメソッドがクラスメッドとして動作する.
メソッドの可視性と組み込み関数
メソッドの可視性
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
- インスタンス変数は, インスタンスに値が格納される
- トップレベルで初期化された @v1 は, トップレベルの Object のインスタンスに格納される
- @v2 はクラスの中で値が代入されているので Qux3 クラスに格納される
- インスタンス変数はメソッドのように探索されず, インスタンスの中だけで簡潔し, 生成元クラスも継承チェーンも辿らない
インスタンス変数をインスタンス内に格納し, 外部からアクセスするには, インスタンスメソッド内部で初期化し, アクセサメソッドを定義する必要がある.
[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 => "定数が見つからないよ"
以上
写経でした.