【Ruby】クラスとモジュール

「はじめてのRuby」を読んでのメモ。
今回は8章「クラスとモジュール」


クラス

ary = []
str = "Hello"
p ary.class    #=> Array
p str.class    #=> String
# オブジェクトがどのクラスに属しているか
p ary.instance_of?(Array)     #=> true
p str.instance_of?(String)    #=> true
p ary.instance_of?(String)    #=> false
p ary.instance_of?(Array)     #=> false
# 特定のクラスのインスタンスかどうかの判断
p ary.is_a?(Array)     #=> true
p ary.is_a?(Object)    #=> true
# 継承関係をさかのぼって判断できる


クラスを作る

class HelloWorld                     #=> class 文
  def initialize(myname = "Ruby")    #=> initialize メソッド
    @name = myname                   #=> インスタンス変数の初期化
  end

  def hello                          #=> インスタンスメソッド  
    puts "Hello, world. I am #{@name}."
  end
end

secon = HelloWorld.new("secon")
alice = HelloWorld.new("Alice")
ruby = HelloWorld.new

secon.hello                          #=> Hello, world. I am secon.
ruby.hello                           #=> Hello, world. I am Ruby.

この例を細分化する。

class 文
class HelloWorld    # クラス名は必ず大文字で始める
  クラスの定義
end


initialize メソッド

new メソッドによってオブジェクトを生成すると、この文が呼ばれる。そのとき、new に渡した引数がそのまま渡される。

def initialize(myname="Ruby")
  @name = myname
end
# 引数 myname を受け取っている
secon = HelloWorld.new("secon")
# initialize メソッドに "secon" を渡している
ruby = HelloWorld.new
# 引数を渡さなかった場合はデフォルト値(今回の場合は "Ruby")が渡される


インスタンス変数とインスタンスメソッド
def initialize(myname="Ruby")    # initialize メソッド
  @name = myname                 # インスタンス変数の初期化
end

@ ではじまる変数はインスタンス変数であり、同じインスタンス内であればメソッド定義を越えてその値を参照、変更できる。
また、インスタンスごとに違う値を持つこともでき、インスタンスが存在している間は値を保持しておいて何度でも利用できる。

secon = HelloWorld.new("secon")
alice = HelloWorld.new("alice")
ruby = HelloWorld.new
# それぞれ異なる @name を保持

インスタンス変数はインスタンスメソッドから参照できる。
hello メソッドでは @name を利用している。

class HelloWolrd

  ...

  def hello         # インスタンスメソッド
    puts "Hello, world. I am #{@name}."
  end
end

HelloWorld クラスのインスンタンスに対して hello メソッドを呼び出すと、initialize メソッドで設定された @name の値が使われる。

secon.hello    #=> Hello, world. I am secon.
ruby.hello     #=> Hello, world. I am Ruby.


アクセスメソッド

Ruby では、オブジェクトの外部からインスタンス変数を直接参照したり、インスタンス変数に代入したりすることができない。オブジェクトの内部の情報にアクセスするためには、そのためのメソッドを定義する必要がある。

HelloWorld クラスの @name にアクセスするために、に次のメソッドを追加する。

class HelloWorld

  ...

  def name            # @name を参照する
    @name
  end

  def name=(value)    # @name を変更する
    @name = value
  end
end
p alice.name    #=> "alice"
# @name の値を返す
alice.name=("Ellie")
# @name の値を変更

p alice.name    #=> "Ellie"



これらのメソッドを簡単に定義するために、次のようなメソッドが用意されている。

定義 意味
attr_reader :name 参照のみ可能(name メソッドを定義)
attr_writer :name 変更のみ可能(name= メソッドを定義)
attr_accessor :name 参照と変更の両方が可能(上記2つを定義)
class HelloWorld
  attr_accessor :name    # 同様の意味となる
end


self メソッド

インスタンスメソッドの中で、メソッドのレシーバ自身を参照するときに使う。
参照したいメソッドが定義されていなければ当然呼び出すことは出来ない。

class HelloWorld

  ...

  def greet
    puts "Hi, I am #{self.name}."    #greet メソッドを呼んだ時のレシーバを参照
  end
end

レシーバを省略してメソッドを呼ぶと、暗黙に self をレシーバとする。

def greet
  print "Hi, I am #{name}."
end

「=」で終わるメソッドを呼び出す場合はレシーバを明示して、「self.name = "Ruby"」という形式で呼ぶ必要がある。

def test
  name = "Ruby"
  self.name = "Ruby"
end

・・・この文が何をやっているか、いまいちよく分からない。

クラスメソッド

クラスメソッドはインスタンスに対する操作ではなく、そのクラスに関連する操作のために使われる。

class << HelloWorld
  def hello(name)
    puts "#{name} said hello."
  end
end

HelloWorld.hello("John")    #=> John said hello.

「class << クラス名 ~ end」という書き方のクラス定義を特異クラス定義という。
また、特異クラスで定義したメソッドを特異メソッドという。

次のような形式でクラス定義することも出来る。

def HelloWorld.hi(name)
  puts "#{name} said Hi."
end

HelloWorld.hi(Taro)    #=> Taro said Hi.



クラス定義の中でクラスメソッドを追加する場合は、「class << self ~ end」として、その中にメソッドを記述することも出来る。

class HellWorld
  class << self
    def hello(name)
      puts "#{name} said hello."
    end
  end
end

クラス定義の中なら次のような形式で定義することもできる。

class HelloWorld
  def self.hi(name)
    puts "#{name} said Hi."
  end
end


クラス変数

「@@」で始まる変数で、そのクラスの全てのインスタンスで共有できる変数のこと。定数と違い、何度でも値を変更することが出来る。

class HelloCount    
  @@count = 0             # hello メソッドの呼び出し回数  

  def HelloCount.count    # 呼び出し回数を参照するためのクラスメソッド
    @@count
  end

  def initialize(myname="Ruby")
    @name = myname
  end

  def hello
    @@count += 1          # 呼び出し回数を加算
    puts "Hello, world. I am #{@name}."
  end
end

secon = HelloCount.new("secon")
alice = HelloCount.new("alice")
ruby = HelloCount.new

p HelloCount.count        #=> 0
secon.hello
alice.hello
ruby.hello
p HelloCount.count        #=> 3


メソッドの呼び出しを制限

Ruby では3種類のアクセス制限のレベルが用意されている。

  • public 

メソッドを、インスタンスメソッドとして使えるように公開する

  • private 

メソッドを、レシーバを指定して呼び出せないようにする(インスタンスの外側から利用できなくする)

  • protected

メソッドを、同一のクラスであればインスタンスメソッドとして使えるようにする


メソッドのアクセス制限を変更するには、これらのキーワードにメソッド名を表すシンボルを指定する。

class AccTest
  def pub
    puts "pub is a public method."
  end

  public :pub    # pub メソッドを public に設定(指定しなくてもよい)

  def priv
    puts "priv is a private method."
  end

  private :priv  # priv メソッドを private に設定
end

acc = AccTest.new
acc.pub          #=> pub is a public method.
acc.priv         #=> private method `priv' called for #<AccTest:0x00000002fc3760> (NoMethodError)
# priv メソッドを呼び出そうとすると例外が発生する

複数のメソッドをまとめて同じアクセス制限に定義したい場合は次のようにすることもできる。

private    # 引数を指定しなければ、これ以降に定義したメソッドは private になる

def priv
  puts "priv is a private method."
end

def priv2
  puts "priv2 is a private method."
end

initialize メソッドは特別で、常に private として定義される。


protected は、同一クラス(とそのサブクラス)以外の場所からは呼び出せないようにする。

class Point
  attr_accessor :x, :y    # アクセスメソッドを定義
  protected :x=, :y=      # x= と y= を protected

  def initialize(x=0.0, y=0.0)
    @x, @y = x, y
  end

  def swap(other)         # x,y の値を入れ替えるメソッド
    tmp_x, tmp_y = @x, @y
    @x, @y = other.x, other.y
    other.x, other.y = tmp_x, tmp_y  #同一クラス内では呼び出し可能

    return self
  end
end

p0 = Point.new
p1 = Point.new(1.0, 2.0)
p [ p0.x, p0.y ]    #=> [0.0, 0.0]
p [ p1.x, p1.y ]    #=> [1.0, 2.0]

p0.swap(p1)
p [ p0.x, p0.y ]    #=> [1.0, 2.0]
p [ p1.x, p1.y ]    #=> [0.0, 0.0]

p0.x = 10.0         #=> NoMethodError



長くなりそうなので一旦ここまで。