読者です 読者をやめる 読者になる 読者になる

ピッケル本を読む(2)第3章 クラス、オブジェクト、変数

ruby 勉強

ジュークボックスに入れる曲をクラスと想定して説明を進めるらしい。Songクラスなどの曲を表すクラスを書いて動作を確認しながら進む。

メモ

  • newするとinitializeメソッドが呼び出される(オブジェクトの初期化に使う)
  • メソッドの引数はメソッド内のローカル変数と同じ様に動作する
class Song
  def initialize(name, artist, duration)
    @name = name
    @artist = artist
    @duration = duration
  end
end

song = Song.new("Bicylops", "Fleck", 260)
p song.inspect    #=>    "#<Song:0x7fdbb7d8 @duration=260, @name=\"Bicylops\", @artist=\"Fleck\">"
p song.to_s       #=>    "#<Song:0x1002f96c>"
  • Rubyでは、クラス定義は固定されず、動的に変更可能
  • 従って、既存のクラスにいつでもメソッドを追加できる
  • ためしに、to_sメソッドをオーバーライドしてみる(クラス内で既に書いた部分は再度書かないが、実際は存在する)
class Song
  def to_s
    "Song: #@name--#@artist (#@duration)"
  end
end

song = Song.new("Bicylops", "Fleck", 260)
p song.to_s       #=>    "Song: Bicylops--Fleck (260)"
  • Rubyで継承はこう書く
# class SubClass < SuperClass
class KaraokeSong < Song
  def initialize(name, artist, duration, lyrics)
    # superの呼び出し元メソッド(ここではititialize)と同じ名前のメソッドを親クラスから呼び出す
    super(name, artist, duration, lyrics)
    @lyrics = lyrics
  end
end

song = KaraokeSong.new("My way", "sinatra", 225, "And now, the...")
# 自分のクラスにメソッドが実装されてなければ、次々と親クラスを探しに行く
# ここでは、Songクラスのto_sメソッドが呼び出される
p song.to_s    #=>    "Song: My way--sinatra (225)"
  • 情報分離の考え方
    • 子クラスでは親クラスの内部状態を参照しない
保守性の良い例(親クラスと子クラスの情報分離が出来ている)
# 親クラスのメソッドto_sをオーバーライドして、KaraokeSongクラスで歌詞(lyrics)を表示できるようにする
class KaraokeSong < Song
  def to_s
    # 親クラスの実装に依存しない
    super + " [#@lyrics]"
  end
end

song = KaraokeSong.new("My way", "sinatra", 225, "And now, the...")
p song.to_s    #=>    "Song: My way--sinatra (225) [And now, the...]"
悪い例(実行結果は同じだが、保守性が悪い)
# 親クラスのメソッドto_sをオーバーライドして、KaraokeSongクラスで歌詞(lyrics)を表示できるようにする
class KaraokeSong < Song
  def to_s
    # 親クラスのインスタンス変数を直接参照している
    "KaraokeSong: #@name--#@artist (#@duration) [#@lyrics]"
  end
end

song = KaraokeSong.new("My way", "sinatra", 225, "And now, the...")
p song.to_s    #=>    "Song: My way--sinatra (225) [And now, the...]"
  • クラス定義のときに何も指定しないと Object クラスが暗黙に指定される
    • 全てのオブジェクトは Object を祖先に持つ
    • to_sは Object クラスのインスタンスメソッドなので、全てのオブジェクトに対して送信できる
  • Rubyは単一継承のシンプルさと多重継承の強力さを併せ持つらしい
    • Rubyは単一継承言語
    • しかし、任意の数のmixinの機能を取り込む事によって、トラブル無く多重継承と同等の機能を利用できる
    • 結局、mixinって何なんだ?大分先で詳しく説明するらしい
  • オブジェクトと属性
    • 属性=オブジェクトの外部から見える側面(APIのことかな?)
  • Rubyのgetterとsetter(アクセサメソッド)は簡単に、保守性良く書ける
    • :artistという構文は、artistに対応するSymbolオブジェクト(変数artistの名前)を返す式
    • コロン無しのartistは変数artistの値を意味する
保守性の良いゲッター
clsss Song
# Rubyではこの種のアクセサメソッドが用意されてる
# ゲッター
  attr_reader :name, :artist, :duration
end
song = Song.new("Bicylops", "Fleck", 260)
song.artist      #=>    "Fleck"
song.name        #=>    "Bicylops"
song.duration    #=>    260
保守性の悪いゲッター(ゲッターの追加と削除の管理が大変)
clsss Song
# 普通、getterは次のように書けるが、保守性が悪い
  def name
    @name
  end
  def artist
    @artist
  end
  def duration
    @duration
  end
end
song = Song.new("Bicylops", "Fleck", 260)
song.artist      #=>    "Fleck"
song.name        #=>    "Bicylops"
song.duration    #=>    260
保守性の良いセッター
clsss Song
# Rubyではこの種のアクセサメソッドが用意されてる
# セッター
  attr_writer :duration
end
song = Song.new("Bicylops", "Fleck", 260)
song.duration = 257
song.duration    #=>    257
保守性の悪いセッター(セッターの追加と削除の管理が大変)
clsss Song
# セッター
# Rubyでは、等号で終わるメソッドを書くことで、自然に値を代入するように出来る
  def duration=(new_duration)
    @duration = new_duration
  end
end
song = Song.new("Bicylops", "Fleck", 260)
song.duration = 257
song.duration    #=>    257
  • 仮想属性
    • 外から見ると、duration_in_minutesも同じ属性に見える
    • しかし、内部的には、duration_in_minutesに対応するインスタンス変数は存在しない
    • 要は、インスタンス変数であるか、計算によって得られる値であるかという区別を隠すことによって、クラスの実装を外部から保護できる
    • インスタンス変数を無闇に作らないで、内部で参照を統一している(統一参照の原則というらしい)
class Song
  # 仮想属性(外部からは普通の属性と同じに見える)
  def duration_in_minutes
    @duration / 60.0 # 浮動小数点に強制的に変換
  end
  def duration_in_minutes=(new_duration)
    @duration = (new_duration*60).to_i
  end
end

song = Song.new("Bicylops", "Fleck", 260)
p song.duration_in_minutes    #=>    4.33333333333333
# 一見、普通の属性を介して代入しているように見えるが、実際は仮想属性を使ってる
song.duration_in_minutes = 4.2
p song.duration                   #=>    252
  • 属性と普通のメソッドの違い
    • 内部状態は、インスタンス変数として保持される
    • 外部状態は、属性と呼ばれるメソッドを介して外部に公開される
    • 内部状態を外部に見せるという属性の役割以外のアクションが通常のメソッド
    • 要は、内部状態(インスタンス変数)にアクセスするメソッドかどうかを見分けたいだけみたい
  • クラス変数
    • @@valのように、頭に@を2つ付けるとクラス変数とみなされる
    • あるクラスの特定のクラス変数の実体は1つしか存在しない
    • 使用する前に必ず初期化が必要(グローバル変数インスタンス変数は必要ない)
    • クラスとそのインスタンスに閉じた変数
    • 参照するには、インスタンスメソッドか、クラスメソッドのアクセサメソッドが必要
  • クラスメソッド
    • メソッド名の前にクラス名とピリオドを付けて定義する
    • 特定のオブジェクトに関連付けられないメソッドのこと
    • newメソッドもクラスメソッドらしい
class Example
  def instance_method      # インスタンスメソッド
  end

  def Example.class_method # クラスメソッド
  end
end
  • シングルトンの構文
    • オブジェクトの実体を一つしか作れないようにするデザインパターンらしい
    • クラスメソッドの使い方の良い具体例だ
# MyLogger.createを呼び出すことでしかログ記録オブジェクトが生成されないようにする
# また1つしかログ記録オブジェクトが生成されないようにする
class MyLogger
  private_class_method :new
  @@logger = nil

  def MyLogger.create
    @@logger = new unless @@logger
    @@logger
  end
end
  • アクセス制御
    • public:アクセス制御は行われないデフォルトのメソッド保護方式
    • protected:定義元クラスおよびその子クラスのオブジェクトだけが呼び出せる
    • private:明示的なレシーバを指定して呼び出すことは出来ず、レシーバは常に自分自身(self)
  • Rubyのアクセス制御は、プログラム実行時に動的に決定されるらしい
  • 変数
    • 変数は単にオブジェクトへのリファレンスに過ぎない
person1 = "Tim"
person2 = person1

person1[0] = 'J'

# person1とperson2は同じオブジェクトを指してる
p person1    #=>    "Jim"
p person2    #=>    "Jim"
person1 = "Tim"
# Stringのdupメソッドで、同じ中身の新しいStringオブジェクトが作成される
person2 = person1.dup

person1[0] = 'J'

# person1とperson2は違うオブジェクトを指してる
p person1    #=>    "Jim"
p person2    #=>    "Tim"

まとめ

クラス、オブジェクト、変数の詳細について勉強した。シングルトンが簡単に実装できるのが面白いと思った。何かを一元管理したいときに便利な書き方なのかも知れない。