STUDY MEMO

学習のメモ書き

<Ruby> クラス

クラスとメソッドの関係性(メソッド編でも記載)

クラスやメソッド、オブジェクトは以下のように定義されることが多い。

クラス:設計図
オブジェクト:実体
メソッド:振る舞い

イメージとしては
クラス(設計図)内にメソッド(振る舞い)があって、

オブジェクト(実体)はクラス(設計図)を元に作られたもの


または、別の例えだと、クラスを生物の種族と捉えるというのもよくある。
クラス(猫)は、なく、寝るなどの基本的な行動(振る舞い)があり、
または猫はそれぞれ個体がある。


クラス

クラスとは、オブジェクトの所属を表すもの。
クラスに属するオブジェクトは、そのクラスのインスタンスであるとも表現されるが、そのクラスに属しているなどの意味を強調したいときに使われる。 オブジェクトは所属するクラスのメソッドを使用することができる。
どのクラスに属するかはclassメソッドで調べることができる。

"abc".class
=> String
1.class
=> Integer
[1,2,3].class
=> Array
{id: 1}.class 
=> Hash


クラスを作る

クラスを作るには、以下のような形式で記載する。

class クラス名 
  ~
end

class ~ endの中にメソッドなどを記載していく。
クラス名は先頭が大文字で、その後ろは小文字になるのが慣習。
また、2単語語以上を組み合わせる際はキャメルケースで記載する。

命名規則の表現とは?
sample imageという語句があった時、
キャメルケースではsampleImageと表現され、
スネークケースではsample_imageと表現される。
そのほかケバブケース(sample-image)もある。

クラスを定義するとクラス名は定数として設定される。


オブジェクトの作り方

自作のクラスのオブジェクトはクラスメソッドnewを使用する。
その他StringやArrayなどのクラスのオブジェクトは、以下のように作成できる。

class Sample
  def dish
    puts "お皿"
  end
end

Sample.new
# => #<Sample:0x00007fd89a06f138>

# ArrayやStringなどのオブジェクト
a = "string"  #  => "string"
b = [1,2,3]  #  => [1, 2, 3]
String.new  #  => ""
Array.new  #  => []

ちなみにIntegerのように、newメソッドが存在しないクラスもある。

Integer.new  #  =>  NoMethodError (undefined method `new' for Integer:Class)


classメソッド

classメソッドを使用するとオブジェクトのクラスを調べることができる。

# irb
Sample.new.class  #  => Sample
Sample.new.class.class  #  => Class

実行結果からSample.newはsampleクラスのオブジェクトを指し、そのオブジェクトのクラスはSample、
SampleクラスのクラスはClassだということがわかる。
また以下の結果から、クラスもオブジェクトの一種とわかる。

Sample.class  #  => Class


クラスにメソッドを定義

クラス内にメソッドを書くことをメソッドを定義する、という。
メソッドを定義することで、そのクラスに属する全てのオブジェクトを呼び出せる。
冒頭で猫を使って解説したものでいうならば、
Catクラスに属するミケたちは「食べる」などの振る舞いを使える、ということ。

class Cat
  def eat
    "もぐもぐ"
  end
end

# 変数mikeに生成したCatクラスのオブジェクトを代入
mike = Cat.new  #  => #<Cat:0x00007fd89684df98>
# Catクラスに定義されたeatを呼び出し
p mike.eat  #  => "もぐもぐ"


レシーバ

メソッドを呼び出すオブジェクトのことをレシーバという。 Cat.newのCat、mike.eatのmikeがレシーバ部分にあたる。


methodsメソッド

methodsメソッドは、レシーバであるオブジェクトが呼び出せるメソッドを表示するメソッド。

class Cat
  def eat
    "もぐもぐ"
  end
  def purr
    "ぐるぐる.."
  end
end

mike = Cat.new

mike.methods
=> [:purr, :eat,   #<-定義したメソッド
:instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :instance_variable_set, :protected_methods, :instance_variables, :private_methods, :method, :public_method, :public_send, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :class, :nil?, :hash, :dup, :singleton_class, :clone, :then, :itself, :yield_self, :untaint, :taint, :tainted?, :trust, :untrust, :untrusted?, :singleton_methods, :frozen?, :methods, :public_methods, :equal?, :!, :==, :instance_exec, :!=, :instance_eval, :__id__, :__send__]

# sortメソッドを使ってabc順に並べ替え
mike.methods.sort
=> [:!, :!=, :!~, :<=>, :==, :===, :=~, :__id__, :__send__, :class, :clone, :define_singleton_method, :display, :dup, :eat, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :methods, :nil?, :object_id, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :purr, :remove_instance_variable, :respond_to?, :send, :singleton_class, :singleton_method, :singleton_methods, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :untaint, :untrust, :untrusted?, :yield_self]


引数のあるメソッドの定義

クラス内のメソッドにも引数でオブジェクトを渡すことができる。

class Cat
  def eat(food)
    "#{food}をもぐもぐ"
  end
  def purr(sound)
    "#{sound}"
  end
end

mike = Cat.new
p mike.eat('')  #  =>  "魚をもぐもぐ"
p mike.purr('にゃー')  #  =>  "にゃー"


クラス内で同じクラスのメソッドの呼び出し

同じクラス内のメソッドの呼び出しは、メソッド名を書くことで実行できる。
メソッドのレシーバは省略すると実行中のメソッド(eat)のレシーバ(mike)が呼び出される。

class Cat
  def eat(food)
    "#{food}" + eat_sound
  end  
  def eat_sound
    "もぐもぐ"
  end
end

mike = Cat.new
p mike.eat('')  #  =>  "魚をもぐもぐ"


self

selfは擬似変数で、現在のメソッドの実行主体 (オブジェクトまたはクラス)が入る。 以下の例ではeat_soundメソッドのレシーバを調べるため、eatメソッド内でselfを使用している。

class Cat
  def eat(food)
    p self
    "#{food}" + eat_sound
  end  
  def eat_sound
    "もぐもぐ"
  end
end


mike = Cat.new  

p mike
#  =>  #<Cat:0x00007fa9450a9628> -> mikeオブジェクト識別番号

p mike.eat('') 
#  =>  #<Cat:0x00007fa9450a9628>  -> eat_soundのレシーバのオブジェクト識別番号
#  =>  "魚をもぐもぐ"

0x~は識別番号で、0x以下は実行するたびに変更される。
この番号が同じであれば同じオブジェクトであるので、同じレシーバといえる。
そのため、eatメソッドで記載しているeat_soundの呼び出しをselfを省略せず、self.eat_soundともかくことができる。
例の処理の流れを書くと以下の通りになる。


インスタンス変数

@変数と表現される変数で、設定されたオブジェクトで使用することができ、そのクラスまたはサブクラスのメソッドを参照することができる。

class Cat
  def eat(food)
    p "#{food}を食べている"
      food = food
  end  
  def refill
    p "#{food}をまだ食べたそうにしている"
  end
end

mike = Cat.new
mike.eat('')  #  =>  "魚を食べている"
mike.refill  #  =>  NameError (undefined local variable or method `food' for #<Cat:0x00007fa941968858>)

「foodは定義されていない変数または変数である」といったエラーが出ている。
これはローカル変数のスコープの領域を超えたのでfoodという変数のデータが消失したのが原因。
そのためオブジェクトで保持できるインスタンス変数にして改めて実行すると、

class Cat
  def eat(food)
    p "#{food}を食べている"
      @food = food
  end  
  def refill
    p "#{@food}をまだ食べたそうにしている"
  end
end

mike = Cat.new
mike.eat('')
mike.refill
"魚を食べている"
"魚をまだ食べたそうにしている"

流れは以下のような流れになる。

newメソッドで作られたオブジェクトがインスタンス変数の持ち主になるため、この時はmikeオブジェクトだけが@food = '魚'の情報を保持している。

mike = Cat.new
tama = Cat.new
mike.eat('')  #  =>  "魚を食べている"
tama.eat('ちゅ〜る')  #  =>  "ちゅ〜るを食べている"
mike.refill  #  =>  "魚をまだ食べたそうにしている"
tama.refill  #  =>  "ちゅ〜るをまだ食べたそうにしている"


インスタンス変数を取得(getter)

オブジェクトの外でインスタンス変数を取得するには、取得用のメソッドを作成する必要がある。
また、取得するためのメソッドはゲッターと表現される。
ゲッターなしで呼び出そうとする以下のように構文エラーが出る。

class Cat
  def eat(food)
    p "#{food}を食べている"
      @food = food
  end  
end

mike = Cat.new
mike.eat('')  #  =>  "魚を食べている"
p mike.@food
#  =>  SyntaxError (sample.rb:10: syntax error, unexpected tIVAR)
p mike.@food

ゲッターであるメソッドを追加すると以下のようになる。
ゲッターで@foodの情報をfoodメソッドで展開できるように設定。

class Cat
  def eat(food)
    p "#{food}を食べている"
      @food = food
  end
  def food  #<- ゲッター
    @food
  end
end

mike = Cat.new
mike.eat('')  #  =>  "魚を食べている"
p mike.food  #  =>  "魚"

このゲッターをattr_readerというメソッドで1行で記載することもできるが、また別の投稿で記載する。


インスタンス変数へ代入(setter)

インスタンス変数へ代入するためのメソッドは=()を使用する。 代入するためのメソッドはセッターと表現される。

class Cat
  def food=(name)
    @food = name
  end
  def food
    @food
  end
end

mike = Cat.new
mike.food = ""
puts mike.food  #  =>  魚
mike.food = "ちゅ〜る"
puts mike.food  #  =>  ちゅ〜る

()=メソッドはインスタンス変数から@をとり、末尾に=をつけたものを使用する慣習がある。


オブジェクトのもつインスタンス変数を表示

instance_variablesメソッドを使用すると、オブジェクトのインスタンス変数名をシンボルの配列として返す。

class Cat
  def eat(food)
    p "#{food}を食べている"
      @food = food
  end
  def food
    @food
  end
end

mike = Cat.new
mike.food = ""
p mike.instance_variables  #  =>  [:@food]

mike.foodで@foodに@food = "魚"が定義されるので、mikeオブジェクトには@foodが定義されるようになる。 そのためp mike.instance_variablesで[:@food]が返される。


オブジェクトの初期化

initializeメソッドはオブジェクト初期化メソッドであり、Objectクラスのprivateメソッドに分類される。
このメソッドはnewインスタンスで作成されたオブジェクトの初期化のため使用される。
newメソッドから自動で呼び出される。

class Cat
  def initialize
    puts "initializeメソッド"
  end
end

Cat.new  #  =>  initializeメソッド

インスタンス変数の初期値を設定

initializeメソッドを使用することで、インスタンス変数の初期値を設定することができる。

class Cat
  def initialize
    @color = "ぶち"
  end
  def color
    @color
  end
end

mike = Cat.new
puts mike.color  #  =>  ぶち


引数を渡す場合
class Cat
  def initialize(color)
    @color = color
  end
  def color
    @color
  end
end

mike = Cat.new("しろ")
puts mike.color  #  =>  しろ


インスタンスメソッドとクラスメソッド

インスタンスメソッド:インスタンス(オブジェクト)をレシーバにするメソッド(mike.colorなど)
クラスメソッド:クラスをレシーバにするメソッド(Cat.newなど)


クラスメソッドの定義
selfを使った方法

メソッド名の前にself.という記載を入れるとクラスメソッドに定義される。
これは前述の通りselfが擬似変数であり、実行主体の情報が入っているため。
クラス名.クラスメソッドという形でも定義できるが、クラス名が変更になった場合保守性が低くなるためあまり使用しない。

class Cat
  def self.eat_sound
    "もぐもぐ"
  end
end

puts Cat.eat_sound  #  =>  もぐもぐ


<<

複数のクラスメソッドをまとめて書くときに有効。

class Cat
  class << self
    def eat_sound
      "もぐもぐ"
    end
  end
end

puts Cat.eat_sound  #  =>  もぐもぐ


#記法と.記法
インスタンスメソッドは「クラス名#メソッド名」、
クラスメソッドは「クラス名.メソッド名」または「クラス名::メソッド名」
を使って表される。


クラス内のクラスメソッドの呼び出し

インスタンスメソッド同様メソッド名だけでも呼べるが、省略されているクラス名またはselfでも呼べる。

class Cat
  def self.eat_sound
    "もぐもぐ"
  end
  def self.eat_sound_continue
    eat_sound + "ぱくぱく"  # 省略形
    # self.eat_sound + "ぱくぱく"
    # Cat.eat_sound + "ぱくぱく"
  end
end

puts Cat.eat_sound_continue  #  =>  もぐもぐぱくぱく

インスタンスメソッドはクラスメソッドを呼ぶことできるが、逆はできない。
なぜならクラスからオブジェクトのレシーバを判別することはできないため。


継承について

子クラスで、子クラス < 親クラスのような記載をすると継承が定義される。
継承が定義された子クラス(サブクラス)では、親クラス(スーパークラス)のメソッドが使用できる。

class Animal
  def name
    @name
  end
  def name=(text)
    @name = text
  end
end

class Cat < Animal
  def size
    @size
  end
  def size=(text)
    @size = text
  end
end

mike = Cat.new
mike.name = "みけ"
mike.size = "小さい"

puts "#{mike.name}#{mike.size}"

ここではnameメソッドのない子クラスのCatが、親クラスAnimalのnameメソッドを使っている。


継承関係を確認する

ancestorsメソッドは親クラスとincludeしているモジュールを順番に配列に格納して返す。

p Cat.ancestors
#  =>  [Cat, Animal, Object, Kernel, BasicObject]


オーバーライド

親クラスと子クラスで同じ名前のメソッドがある時、子クラスのメソッドが呼ばれる。 これをオーバーライドという。

class Animal
  def name
    @name
  end
  def name=(text)
    @name = text
  end
  def medical_chart
    @name
  end
end

class Cat < Animal
  def size
    @size
  end
  def size=(text)
    @size = text
  end
  def medical_chart
    "名前:#{@name} 大きさ:#{@size}"
  end
end

mike = Cat.new
mike.name = "ミケ"
mike.size = "ちいさい"
puts mike.medical_chart  #  =>  名前:ミケ 大きさ:ちいさい


親クラスメソッドの使用
class Animal
  def name
    @name
  end
  def name=(text)
    @name = text
  end
  def medical_chart
    puts ""
  end
end

class Cat < Animal
  def size
    @size
  end
  def size=(text)
    @size = text
  end
  def medical_chart
    super
    puts "名前:#{@name}  大きさ:#{@size}"
  end
end

mike = Cat.new
mike.name = "ミケ"
mike.size = "ちいさい"
mike.medical_chart

# 結果
猫
名前:ミケ  大きさ:ちいさい

上記の例では、superで親クラスのメソッドが呼ばれ、その次に子クラスへ処理が戻ってきている。
また、#{}の中にもsuperは入れることができる。


メソッドの制限

privateを設定すると、private以下のメソッドについて、クラス外での呼び出しについて変数でしか呼び出せなくなるようにできる。

class Cat
  def eat
    food
  end

  private

  def food
    ""
  end
end

mike = Cat.new
p mike.eat  #  =>  "魚"
p mike.food
#  =>  NoMethodError (private method `food' called for #<Cat:0x00007fa47d046158>)

privateの他にpublic(アクセス制限なし)やprotected(アクセス制限あり)もある。
一度privateの記載をするとそれ以降はprivate設定になるので、もしその下にpublicなメソッドを書きたい場合、privateと同じようにpublicを入力するとそれ以降publicな状態のメソッドになる。

privateの書き方はもう一つあり、以下のようにも記載できる。

class Cat
  def eat
    food
  end

  private def food
    ""
  end
end

mike = Cat.new
p mike.eat  #  =>  "魚"
p mike.food
#  =>  NoMethodError (private method `food' called for #<Cat:0x00007fa47d131158>)


クラスメソッドの制限

private_class_methodをdefの前に記載するとクラスメソッドを制限できる。

class Cat
  private_class_method def self.eat
    puts "もぐもぐ"
  end
end

Cat.eat
#  =>  NoMethodError (private method `eat' called for Cat:Class)

class << selfの場合はprivateが使用できる。

class Cat
  class << self
    private
    def eat_sound
      "もぐもぐ"
    end
  end
end

puts Cat.eat_sound
#  =>  NoMethodError (private method `eat_sound' called for Cat:Class)


参考文献

命名規則「キャメルケース」「スネークケース」「ケバブケース」についてまとめてみました | オウンドメディア | 大阪市天王寺区ホームページ制作 | 合同会社デザインサプライ-DesignSupply. LLC-

変数と定数 (Ruby 3.0.0 リファレンスマニュアル)

Rubyの「attr_accessor」ってなんぞや、という人へ | とむじそブログ

Module#private (Ruby 3.0.0 リファレンスマニュアル)

ゼロからわかる Ruby 超入門 (かんたんIT基礎講座) | 五十嵐 邦明, 松岡 浩平 |本 | 通販 | Amazon