Ruby学習 「プロを目指す人のためのRuby入門」 7章

はじめに

プロを目指す人のためのRuby入門Rubyを学習しています。
大事そうな部分や理解が難しかった部分、忘れそうなことについて学習メモを書きます。
今回は第7章「クラスの作成を理解する」について書いていきます。

記事中のプログラムはirbで動作を確認しました。

オブジェクト指向プログラミングの基礎知識

  • レシーバはメソッドを呼び出されたオブジェクトのこと
  • メッセージはメソッドのこと(~というメッセージを送っている。というような使い方)
  • 状態(ステート)はオブジェクト毎に保持されるデータのこと

クラスの定義

  • クラス名は必ず大文字で始め、キャメルケースで書くのが一般的
class Book
end
  • クラス名.newでオブジェクトを作成する
  • newメソッドを使うと初期化処理用のinitializeメソッドが呼ばれる(定義しなくてもよい)
class Book
  def initialize(title)
    puts "title: #{title}"
  end
end
  • initializeメソッドはデフォルトでprivateメソッドになっている
  • クラス構文の内部で定義したメソッドはインスタンスメソッドになる
  • インスタンス変数はインスタンス内部で共有される変数で、変数名を@で始める
class Book
  def initialize(title)
    @title = title
  end

  def  info
    puts "Book title is #{@title}"
  end
end

book = Book.new('Introduction to Ruby')
book.info
  • インスタンス変数は作成する前に参照してもエラーにならず、nilが返る
  • インスタンス変数はクラス外部からアクセスできないので、getter、setterでアクセスする
  • Rubyでは=で終わるメソッドを定義すると変数に代入するような形でそのメソッドを呼び出せる
class Book
  def initialize(title)
    @title = title
  end

  def title
    @title
  end

  def title=(value)
    @title = value
  end
end
=> :title=

book = Book.new('Introduction to Ruby')
=> #<Book:0x00000001d001c8 @title="Introduction to Ruby">
book.title = 'Introduction to Python'
=> "Introduction to Python"
book.title
=> "Introduction to Python"
  • attr_accessorメソッドを使用するとgetterとsetterの定義を省略できる
  • attr_readerメソッドを使用するとインスタンス変数を読み取り専用にできる
  • attr_writerメソッドを使用するとインスタンス変数を書き込み専用にできる
class Book
  attr_accessor :title, :author
  attr_reader :price
  attr_writer :page

  def initialize(title, author, price, page)
    @title = title
    @author = author
    @price = price
    @page = page
  end
end

book.title = 'Introduction to Ruby'
=> "Introduction to Ruby"
book.title
=> "Introduction to Ruby"

book.author = 'sato jiro'
=> "sato jiro"
book.author
=> "sato jiro"

book.price = 2000
NoMethodError: undefined method `price=' for #<Book:0x000000022e8b58>
book.price
=> 1000

book.page = 250
=> 250
book.page
NoMethodError: undefined method `page' for #<Book:0x000000022e8b58>
  • Rubyでは同名のクラスを定義すると既存のクラスが上書きされる

  • クラスメソッドを定義するにはメソッド名の前にself.をつけるかclass << selfからendの間にメソッドを記述する

  • クラスメソッドはクラス名.メソッド名で呼び出す
  • クラス中に定数を定義でき、インスタンスメソッド内でもクラスメソッド内でも参照可能
  • 定数名は大文字で始める必要があり、アルファベットの大文字と数字、アンダーバーを使用するのが慣習となっている

selfキーワード

  • selfはインスタンス自身を表す(Javaで言うthis)
  • =で終わるsetterメソッドを呼び出すときは必ずselfをつける(つけないとローカル変数への代入として扱われる)
  • クラスメソッド内のselfはクラス自身を表し、インスタンスメソッド内のselfはインスタンスを表す
  • クラスメソッドからインスタンスメソッドを呼び出すことはできない
  • インスタンスメソッドからクラスメソッドを呼び出すにはクラス名.メソッド名と記述する
  • クラス構文の直下でクラスメソッドを呼び出せるが、クラスメソッドの定義より下で呼び出さなければならない

オブジェクトのクラスを確認する

  • オブジェクトのクラスを調べる場合はclassメソッドを津克
  • instance_of?メソッドを使うと指定したクラスのインスタンスかを調べられる
  • is_a?メソッドを使うと指定したクラスと継承関係(is-a)にあるか調べられる
  • クラスを継承する場合の構文はclass サブクラス < スーパクラス
  • superでスーパクラスの同名メソッドを呼び出せる
  • スーパクラスとサブクラスで引数の数が同じなら、引数なしでsuperを呼ぶと自分に渡された引数をスーパクラスに渡すことができる
  • サブクラスでスーパクラス内の同名メソッドを定義するとオーバーライドできる
  • クラスを継承するとクラスメソッドも継承される

メソッドの公開レベル

  • publicキーワードを書き、そこから下にメソッドを定義するとpublicメソッドになる
  • publicメソッドはクラス外部からでも呼び出せる
  • initializeメソッド以外のインスタンスメソッドはデフォルトでpublicメソッドになる
  • privateキーワードを書き、そこから下にメソッドを定義するとprivateメソッドになる
  • privateメソッドはクラス外部からは呼び出せず、クラス内部からのみ呼び出せる
  • privateメソッドはレシーバを指定して呼び出せないメソッドで、クラス内部でも同様の決まりが適用される
  • Javaとは違い、Rubyのprivateメソッドはサブクラスからも呼び出せる
  • 上記仕様に伴い、スーパクラスのメソッドをオーバーライドできるので、意図せずオーバーライドしないように注意が必要
  • クラスメソッドはprivateキーワードの下に書いてもprivateにならない
  • クラスメソッドをprivateにするには、class << self構文内でprivateキーワードを使うか、private_class_methodを使用してクラスメソッド定義後に公開レベルを変更する
  • publicキーワードとprivateキーワードを使用すると、privateメソッドとpublicメソッドを好きな順番で定義できるが、privateメソッドはクラスの最後にまとめて定義することが多い
  • privateキーワード(privateメソッド)に既存のメソッド名を引数として渡すと、そのメソッドをprivateメソッドにできる
  • privateキーワードに引数を渡した場合はその下に定義したメソッドはprivateにならない
  • protectedキーワードを書き、そこから下にメソッドを定義するとprotectedメソッドになる
  • protectedメソッドはそのメソッドを定義したクラスとサブクラスのインスタンスメソッドからレシーバ付きで呼び出せる
  • protectedメソッドは同じクラスやサブクラスの中からはレシーバ付きで呼び出せるようにしたいときに使用する

定数についてもっと詳しく

  • 定数はクラスの外からも参照可能で、クラス名::定数名の形式で参照する
  • 定数をクラスの外から参照できないようにしたい場合はprivate_constantで定数名を指定するが、Rubyではprivateにすることは少ない
  • Rubyの定数は値の変更が可能なため、値を変更されたくない場合はfreezeする
class Book
  DEFAULT_PRICE = 0
end
=> 0

Book.freeze
=> Book

Book::DEFAULT_PRICE = 1000
RuntimeError: can't modify frozen #<Class:Book>
  • ミュータブルなオブジェクトは変数に代入した状態で変更すると、定数を変更したことに気づきにくくなるので注意する
  • 上記を防ぐためには定数の値をfreezeする
  • 定数の値をfreezeするだけでは配列やハッシュの各要素をfreezeできないため各要素もfreezeする必要がある
 SOME_NAMES = ['Foo', 'Bar', 'Baz'].map(&:freeze).freeze
=> ["Foo", "Bar", "Baz"]
SOME_NAMES[0].upcase!
RuntimeError: can't modify frozen String
  • クラスインスタンス変数はインスタンス作成とは関係なく、クラス自身が保持しているデータ
  • クラス直下やクラスメソッドの内部で@で始まる変数にアクセスると、クラスインスタンス変数にアクセスしていることになる
  • クラスインスタンス変数はスーパクラスとサブクラスでは同じ名前でも別の変数になる
  • クラス変数は変数名の最初に@@をつける
  • クラス変数はクラスメソッド内でもインスタンスメソッド内でも共有され、スーパクラスとサブクラスでも共有される
  • グローバル変数は変数名を$で始める
  • グローバル変数はクラスの内外を問わず、プログラムのどこからでも変更・参照できる

クラス定義やRubyの言語仕様に関する高度な話題

  • クラスはネストすることが可能
  • ネストしたクラスは::を使って参照する
class User
  class BloodType
    attr_reader :type

    def initialize(type)
      @type = type
    end
  end
end
=> :initialize

 blood_type = User::BloodType.new('B')
=> #<User::BloodType:0x00000002031338 @type="B">
blood_type.type
=> "B"
  • クラスのネストはクラス名の衝突を防ぐための名前空間を作る場合によく使われるが、モジュールを使う方が多い

  • Rubyでは演算子のように見えてメソッド都市定義されているものがあり、クラスごとにオーバライドして再定義することができる

  • Rubyでは等値の判断にequal?、==、eql?、===のようなメソッドや演算子がある
  • equal?メソッドはobject_idが等しい場合にtrueを返す
  • ==はオブジェクトの内容が等しい場合にtrueを返す
  • eql?メソッドはhashのキーとして2つのオブジェクトが等しければtrueを返す
  • ==では等しいとみなしてもハッシュのキーとしては異なる値として扱う場合は、eql?メソッドにその要件を定義する
  • eql?メソッドを再定義した場合はa.eql?(b)が真なら、a.hash == b.hashも真となるようにhashメソッドも再定義しなければならない

  • ===は主にcase文のwhen節で使われる

  • 独自に定義したクラスのオブジェクトをcase文のwhen節で使いたい場合は、===を要件に合わせて再定義する必要がある
  • Rubyのクラスは変更に対してオープンで、オープンクラスと呼ばれることもある
  • Rubyはクラスの継承に制限がなく、StringクラスやArrayクラス等の組み込みライブラリのクラスでも継承して独自のクラスを定義できる
  • 定義済みのクラスにメソッドを追加したり、メソッド定義を上書きすることもできる
  • 既存の実装を上書きし、挙動を変更することをモンキーパッチと呼ぶ
  • 既存のメソッドをエイリアスメソッドとして残し、上書きしたメソッドの中で既存のメソッドを再利用することもできる

  • オブジェクト名.メソッド名で指定したオブジェクトにだけメソッドを定義できる(特異メソッド)

  • クラスメソッドは特定のクラスの特異メソッドである
  • オブジェクトのクラスが何であろうとそのメソッドが呼び出せればいいというプログラミングスタイルをダックタイピングという

感想

同じようなキーワードでもJavaと若干仕様が違うものがあるので、混同しないように
意識していく必要がある。また、モンキーパッチやダックタイピングなどのおかげで、
静的型付け言語より柔軟なプログラムを作成できることが分かった。

参考文献

この記事は以下の情報を参考にして執筆しました。