クラス
クラスの定義
class Foo
# 初期化時に実行されるメソッド
def initialize(x)
# インスタンス変数に値を保持
@x = x
end
# インスタンスメソッド
def method1
@x
end
end
クラス名は定数であるため、大文字で始めないと構文エラーとなる。
┗ 定数の中にクラスを格納している。
クラス変数
class Foo
def initialize
@@i = 1
end
def return_i
@@i
end
end
class SubFoo < Foo
def initialize
@@i = 100
end
end
foo = Foo.new
p foo.return_i #=> 1
sub_foo = SubFoo.new
p foo.return_i #=> 100
p sub_foo.return_i #=> 100
クラス定義内やクラスメソッド内で定義され、クラスおよびそのサブクラスのすべてのインスタンスで共有される。
クラス変数の変更は、すべてのインスタンスに影響を与えるため、意図しない変更を引き起こす可能性について注意する必要がある。
グローバル変数
$n = 1
class Foo
$i = 2
def method1; $n; end
def method2; $i; end
end
foo = Foo.new
p foo.method1 #=> 1
p foo.method2 #=> 2
どこからでも参照することが可能。
グローバル変数の乱用は、コードの可読性を低下させ、バグの原因を引き起こす可能性がある。
必要な場合にのみ使用する。※出来る限り使用は避けるべき
インスタンス変数
@n = 1
class Foo
# attr_accessor :n, :i
@i = 2
# def initialize
# @n = 1
# @i = 2
# end
def method1; @n; end
def method2; @i; end
end
foo = Foo.new
# foo.n = 1
# foo.i = 2
p foo.method1 #=> nil
p foo.method2 #=> nil
クラスオブジェクトのインスタンスごとに独自の値を持つ変数。
初期化されていない場合は、nilを返す。
クラスの継承
class Foo
def initialize(x)
@x = x
end
def method1
@x
end
end
class SubFoo < Foo
def initialize(x, y)
@y = y
# スーパークラスの初期化メソッドを呼び出す
super x
end
def method2
@x + @y
end
end
# インスタンス化
sub_foo = SubFoo.new(3, 7)
p SubFoo.superclass #=> Foo
p sub_foo.class #=> SubFoo
p sub_foo.method1 #=> 3
p sub_foo.method2 #=> 10
# p sub_foo.method3 #=> NoMethodError
class サブクラス < スーパークラス
で、指定したオブジェクトを継承したクラスオブジェクトを生成する。
Rubyにおいては、スーパークラスを暗黙的に呼び出すことはない。
クラスからオブジェクトを生成することをインスタンス化と呼ぶ。
継承したクラスのインスタンスは、スーパークラスのインスタンスメソッドを呼び出すことが出来る。
メモリ上では、以下に保持される。
- インスタンスメソッドは、クラスオブジェクト
- インスタンス変数は、インスタンス
クラスの継承チェーン
p Foo.ancestors #=> [Foo, Object, Kernel, BasicObject]
p SubFoo.ancestors #=> [SubFoo, Foo, Object, Kernel, BasicObject]
上記の通り、継承はBasicObjectクラスから始まっている。
スーパークラスを省略してクラスを定義すると、自動的にObjectクラスを継承する。
インスタンスメソッドの探索経路
呼び出し元のクラスに、メソッドが存在するかを判定
→次に、そのスーパクラス(継承元)にメソッドが存在するかの順で判定している
最後まで見つからなかった場合は、NoMethodError
となる
※例外のNoMethodError
は、BasicObjectクラスの#method_missingが呼び出している。
class Foo; end
class SubFoo < Foo
def initialize(x, y)
@x = x
@y = y
end
def method_missing(name)
# 独自の記述追加
puts "******************** method_missing args name: #{name} ********************"
# スーパークラスを呼び出して例外を発生させる
super
end
end
sub_foo = SubFoo.new(3, 7)
p sub_foo.method3 #=> ******************** method_missing args name: method3 ********************
#=> NoMethodError
インスタンスメソッドのネスト
class Foo
def method1
def nested_method1
100
end
1
end
end
# 定義されているメソッドを確認
p Foo.instance_methods(false) #=> [:method1] ⭐️この時点では、nested_method1は定義されていない扱い
foo = Foo.new
# p foo.nested_method1 #=> NoMethodError ⭐️まだ未定義なのでNoMethodErrorとなる
p foo.method1 #=> 1
p Foo.instance_methods(false) #=> [:method1, :nested_method1] ⭐️method1が実行されたことによって定義された
p foo.nested_method1 #=> 100
ネストされたメソッドは、外側のメソッドが呼び出されることで使うことが出来るようになる。
メソッドの可視性(アクセス制限 / カプセル化)
修飾子 | 説明 |
---|---|
public | 同じクラスの、どのインスタンスからでもアクセス可能。 |
protected | 同じクラスと、そのサブクラスからのみアクセス可能。 |
private | レシーバを付けた場合はNoMethodError となる。また、レシーバを付けない場合は、同じクラスと(Rubyでは)そのサブクラスからアクセス可能。 |
class Foo
def public_method
puts 'Public'
end
def all_method
obj = Foo.new
obj.public_method
obj.protected_method
# obj.private_method # レシーバ付きの場合は、NoMethodErrorが発生
private_method # レシーバを外せば、NoMethodErrorは発生しない
end
protected
def protected_method
puts 'Protected'
end
private
def private_method
puts 'Private'
end
end
class SubFoo < Foo
def access_protected_method
protected_method
end
def access_private_method
private_method
end
end
sub_foo = SubFoo.new
sub_foo.access_protected_method #=> Protected
sub_foo.access_private_method #=> Private
# sub_foo.protected_method #=> NoMethodError
# sub_foo.private_method #=> NoMethodError
foo = Foo.new
foo.public_method #=> Public
foo.all_method #=> Public
#=> Protected
#=> Private
# インスタンスメソッドとして、直接呼び出すことは出来ない
# foo.protected_method #=> NoMethodError
# foo.private_method #=> NoMethodError
アクセス制限の変更
class Foo
private
def private_method1
puts 'Private1'
end
def private_method2
puts 'Private2'
end
end
class SubFoo < Foo
# privateから、publicに変更
public :private_method1, :private_method2
end
sub_foo = SubFoo.new
sub_foo.private_method1 #=> Private1
sub_foo.private_method2 #=> Private2
public, protected, privateのいずれかを指定し、メソッド名を識別子
or シンボル
で記載する。複数のメソッドのアクセス制限を変更する場合は、カンマで区切る。
alias
式 と undef
式
class SubFoo < Foo
def initialize(x, y)
@y = y
super x
end
def method2
@x + @y
end
def method3; 1; end
# 新しいメソッド名 古いメソッド名
alias :sub_method_a :method2
alias :sub_method_b :method3
# method3の定義を取り消す
undef :method3
end
sub_foo = SubFoo.new(3, 7)
p sub_foo.sub_method_a #=> 10
p sub_foo.sub_method_b #=> 1
# p sub_foo.method3 #=> NoMethodError
alias式で別名をつける/ undef式で定義を取り消す際のメソッド名は、
識別子
or シンボル
で指定する。
aliasが定義されている場合に、undef式で片方の定義を取り消しても、もう片方が実行出来てしまう。
オープンクラス
class Foo
def method1; 1; end
end
class Foo
def method2; 2; end
end
p Foo.instance_methods(false) #=> [:method1, :method2]
同名のクラスを複数回定義しても、エラーにならない。
定義済みのクラスを再定義出来るクラスのことを、オープンクラスと呼ぶ。
特異クラス(Singleton Class)
- 特異クラスとは、特定のインスタンスだけに適用される特別なクラス
- シングルトンクラスとも呼ばれる
class Foo; end
foo = Foo.new
singleton_foo = class << foo
self # 特異クラス自身を返す
end
p singleton_foo #=> #<Class:#<Foo:0x000000010af94d30>>
p singleton_foo.ancestors #=> [#<Class:#<Foo:0x000000010d094d00>>, Foo, Object, Kernel, BasicObject]
p singleton_foo.superclass #=> Foo
特異クラスは、class << 対象のオブジェクト
で定義することが出来る。
Rubyインタプリタは、特異クラスを作成して継承チェーンに組み込む。
※インスタンスの生成元のクラスを、スーパークラスとしている。
- Rubyインタプリタ
- プログラムのソースコードを読み取り、実行可能な形式に変換して実行するプログラム
- つまり、プログラムを実行するためのプログラムのこと
特異メソッド(Singleton Method)
- オブジェクトの、特定のインスタンスに対して定義されたメソッド
- 特定のインスタンスにのみ影響を与え、そのインスタンスのみで呼び出すことが出来る
- 通常、特異メソッドは、あるインスタンスに対してのみ必要な振る舞いを追加するために使用される
特異メソッドの定義方法1
class Foo
def initialize(num)
@num = num
end
end
foo1 = Foo.new(11)
foo3 = Foo.new(33)
# 定義方法①
def foo1.method1
@num += 100
end
p foo1.method1 #=> 111
# p foo3.method1 #=> NoMethodError
特異メソッドの定義方法2
class Foo
def initialize(num)
@num = num
end
end
foo2 = Foo.new(22)
foo3 = Foo.new(33)
# 定義方法②
class << foo2
def method2
@num += 200
end
end
p foo2.method2 #=> 222
# p foo3.method2 #=> NoMethodError
特定のインスタンスにだけ、特別な性質を持たせたい場面で使用することが出来る。
クラスメソッド
- クラス自体に関連付けられたメソッド
- クラスオブジェクトをレシーバとする
クラスメソッドの定義方法1
class Foo
def Foo.method1
1
end
end
p Foo.method1 #=> 1
クラスメソッドの定義方法2
class Foo
def self.method2
2
end
end
p Foo.method2 #=> 2
クラスメソッドの定義方法3
class Foo
class << self
def method3
3
end
end
end
p Foo.method3 #=> 3
モジュール
- モジュールの目的
- 名前空間の提供と、
Mix-in
における機能の定義
- 名前空間の提供と、
Mix-in:クラスに、モジュールの機能を混ぜ合わせること。
クラスは多重継承が出来ないが、Mix-inをすることで複数の機能を使うことが出来る。(Mix-inによって、多重継承と同等の役割を実現している)
モジュールからインスタンス化(new)することは出来ない。
モジュールは、継承することが出来ない。
モジュールは、クラスや他のモジュールに取り込むことが出来る。
クラス自体にモジュールのメソッドが混ざるのではなく、モジュールに対応した無名クラスがスーパークラスとの間の継承チェーンに挿入される。
メソッドの探索経路(モジュールを組み込んだ場合)
module Module1
def method1; 1; end
def method2; 2; end
def method3; 3; end
def method4; 4; end
end
module Module2
def method1; 10; end
def method2; 20; end
def method4; 40; end
end
class Foo
def method1; 100; end
def method4; 400; end
def method5; 500; end
end
Module#includeの場合
class SubFoo < Foo
include Module1
include Module2
def method1; 1000; end
end
sub_foo = SubFoo.new
p sub_foo.method1 #=> 1000
p sub_foo.method2 #=> 20
p sub_foo.method3 #=> 3
p sub_foo.method4 #=> 40
p sub_foo.method5 #=> 500
includeの場合、呼び出し元のクラス(SubFoo)→Module2→Module1→呼び出し元のスーパークラス(Foo)の順にメソッド探索が行われる。
Module#prependの場合(Ruby2.0~)
class SubFoo < Foo
prepend Module1
prepend Module2
def method1; 1000; end
end
sub_foo = SubFoo.new
p sub_foo.method1 #=> 10
p sub_foo.method2 #=> 20
p sub_foo.method3 #=> 3
p sub_foo.method4 #=> 40
p sub_foo.method5 #=> 500
prependの場合、呼び出し元のクラス(SubFoo)よりも先に、最後にprependされたモジュールから順(Module2→Module1→SubFoo→Foo)にメソッド探索が行われる。
継承チェーン(モジュールを取り込んだ場合)
p SubFoo.ancestors #=> [SubFoo, Module2, Module1, Foo, Object, Kernel, BasicObject]
p SubFoo.superclass #=> Foo
p SubFoo.ancestors #=> [Module2, Module1, SubFoo, Foo, Object, Kernel, BasicObject]
p SubFoo.superclass #=> Foo
- 継承チェーンには、include/prependした
Module1
・Module2
が含まれている - 但し、
SubFoo
のスーパークラスはFoo
のまま
moduleをincludeすると、Rubyインタプリタは指定されたモジュールに対応する無名クラスを作成し、スーパークラスとの間に組み入れる。
対して、prependした場合は先頭に組み入れている。
Object#extend
- 特異クラスへの
Mix-in
で使用するメソッド
Module#include は、クラス(のインスタンス)に機能を追加しますが、extend は、ある特定のオブジェクトだけにモジュールの機能を追加したいときに使用します。
module Module1
def method1; @num += 700; end
end
class Foo
def initialize(num)
@num = num
end
end
foo1 = Foo.new(77)
class << foo1
include Module1
end
p foo1.method1 #=> 777
foo1.extend(Module1)
p foo1.method1 #=> 777
Module#refine(Ruby2.1~)
- 限られた範囲のみ、
Mix-in
を有効化したい場合に使用
引数 klass で指定したクラスまたはモジュールだけに対して、ブロックで指定した機能を提供できるモジュールを定義します。定義した機能は Module#refine を使用せずに直接 klass に対して変更を行う場合と異なり、限られた範囲のみ有効にできます。そのため、既存の機能を局所的に修正したい場合などに用いる事ができます。
class Foo
def method1; 1; end
def method2; 2; end
end
module Module1
# 変更を加えるクラスを宣言
refine Foo do
# 変更するメソッド及び処理を記述
def method1; 10000; end
end
end
foo = Foo.new
p foo.method1 #=> 1
p foo.method2 #=> 2
# usingを呼び出し、変更したメソッドの呼び出しを有効化する
using Module1
p foo.method1 #=> 10000
p foo.method2 #=> 2
usingが有効になる範囲は、クラス・モジュール定義の
- 外でusingを呼び出した場合はファイルの末尾まで
- 中でusingを呼び出した場合はそのクラス・モジュール定義の末尾まで
include
と extend
両方使う場合
module Module1
def method1; 1; end
end
class Foo
include Module1
extend Module1
end
foo = Foo.new
p foo.method1 #=> 1
p Foo.method1 #=> 1
-
include
→モジュール内のメソッドが インスタンスメソッドとして動作する -
extend
→モジュール内のメソッドがクラスメソッドとして動作する
インスタンスメソッドとクラスメソッドの両方として利用可能になる。
ネストした定数の定義・参照
module Module1; end
# Module1の中に、Numberを宣言
Module1::Number = 100
class Foo
include Module1
# 警告が出るが、値を書き換えること自体は可能
Module1::Number = 200
def number
Module1::Number
end
end
# インスタンスを生成しなくても、値は上書きされる
p Module1::Number #=> 200
foo = Foo.new
p foo.number #=> 200
モジュールやクラスオブジェクトが格納された定数に対しては、::
という演算子を使用することで、定数のネストを指定することが可能。
Rubyでは柔軟性を高めるため、定数に値を再代入することが可能となっている。
ただし、本来は再代入を避けるべきである。
名前空間
module Food
class Ramen
def name; 'ラーメン'; end
end
class Cake
def name; 'ケーキ'; end
end
end
puts Food::Ramen.new.name #=> ラーメン
puts Food::Cake.new.name #=> ケーキ
module Guest
class User
def name; 'ゲストユーザ'; end
end
end
module Admin
class User
def name; '管理者'; end
end
end
puts Guest::User.new.name #=> ゲストユーザ
puts Admin::User.new.name #=> 管理者
同じ名前を持つクラスや定数が存在しても、名前の衝突を防ぐことが出来る。
関連するクラスや定数をまとめてグループ化し、構造を整理することが出来る。
適切な名前空間を使用することで、クラスや定数の名前に意味を与えることが出来る。
意味や目的毎にモジュールを分けてクラスを定義することが大切。