バックエンド

【オブジェクト指向入門】Rubyのclass定義方法から継承まで

reisuta

Webエンジニア | 20代中盤 | 大学時代はGmailすら知らないIT音痴でプログラミングとは無縁の生活を送る → 独学でプログラミングを学ぶ → Web系受託開発企業にエンジニアとして就職 → Web系自社サービス企業に転職 | 実務未経験の頃からVimを愛好しており、仕事でもプライベートでも開発はVimとTmuxを使っているので、VSCodeに疎いのが最近の悩み。何だかんだでやっぱりRubyが好き。

本記事では、オブジェクト指向については、解説します。

オブジェクト指向は、プログラミングの伝統的かつ王道の手法なので、
ぜひとも理解しておきたい概念の一つです。

動画は下記をご参考ください。

オブジェクト指向とは?

オブジェクト指向とは、
プログラムをオブジェクトとして構築し、
それらのオブジェクトが相互に作用することによって機能を実現するプログラミング手法のことです。

イメージとしては、学校の先生が生徒(オブジェクト)一人ひとりに
逐一指示を出すのではなく、廊下の張り紙とか校則とかで、
生徒一人ひとりに、行動してもらうという感じです。

これの何がいいかって、先生が逐一指示を出す(すなわち、逐一条件分岐とか
色々コードを書く)とどこかで破綻すると思いますが、
生徒自身(オブジェクト)に振る舞い(メソッド)をもたせることで、
勝手に動いてもらう感じです。

そもそも、システムとは、完成しないものと捉えることもできます。
そのため、何らかの変更が生じるのは避けられないでしょう。

そんな中、そうした変更に対する影響範囲を小さくしたり、
再利用性を高めたりするための一つの手段が、
オブジェクト指向に該当するともいえるでしょう。

すなわち、変更に強いコードを書くための
手段の一つがオブジェクト指向ともいえるでしょう。

オブジェクト指向は、やや難しいかつ、奥が深い概念なので、
最初はピンとこないかもしれませんが、
要は変更に強い効率的なコーディング手法の一つと思ってもらえれば問題ないです。

ユーザーの要求

一般に、ソフトウェア開発においては、
ユーザーが存在し、その要求を満たすために、
開発することが求められるでしょう。

例えば、受託開発が最もわかりやすいと思いますが、
「こういうものを作ってくれ」と要求されると思います。

たいてい、こういった要求は、不完全で、
流動的なのが開発者の悩みの種です。

オブジェクト指向は、変更に強いプログラミング手法なので、
このような流動的で不完全な要求に対する処世術のような側面もあるのかもしれません。

オブジェクト指向言語の代表選手である、
JavaがSierに多いというのも、この辺の事情も
関係しているのかもしれませんね。

裏を返せば、要求も変わらず、
未来においても、仕様が変わらないシステムであれば、
オブジェクト指向など不要なのです。(そんなシステムがあればの話ですが)

さて、抽象的な話はこれぐらいにして、
早速どういうふうに書いていくのか解説していきます。

本記事ではRubyの場合で解説します。
Ruby自体の解説や文法などの解説は、
こちらの記事で行っているので、
併せてご参考ください。

クラスとは?

クラスは、オブジェクト指向における設計図の役割を示すものです。

これだけだと、ピンとこないかもしれませんが、
クラスは、例えるなら学校のクラス名簿みたいなもので、
抽象化された名前のリストでしかなく、
インスタンス化された(いわゆる名簿に載っているその人)
具象物に会うことができないですよね。

その点、クラスというものは抽象的で、
具象化(インスタンス化)に対応するものといった感じです。
(余計分かりにくくなったかな...)

具体例としては、設計図の方が的をいているので、
その例でイメージがつけばそれでOKです。

設計図と言われてもピンとこない場合は、
始業式の学校のクラス名簿を想起して、名前のリスト(抽象物)でしかなく、
それぞれの名前の人にこれから会っていくんだな(インスタンス化)みたいな感じで、
考えるとわかりやすいかも?です。

オブジェクト・インスタンス・レシーバー

オブジェクトは、クラスをもとに作られた具象物のようなものです。
クラス名簿がクラスなら、生徒がオブジェクトにあたりますね。
インスタンスやレシーバーも似たようなもので、ほとんど別名みたいなものです。

インスタンスは、クラスをインスタンス化すると言ったりするので、
その文脈だと、オブジェクトというよりインスタンスと呼ばれることが多い気がします。

レシーバーは、受け取る人という意味があるので、
メソッドが呼び出される側というニュアンスでオブジェクトのことを、
レシーバーといったりします。

それぞれ、指しているものは似たようなものですが、
関係性の中で、どの関係を重視しているかによって、
呼び方が変わっているような感じですかね。

とはいっても、混乱するし、細かい言葉遊びの域のような気もするので、
どれも似たようなものを言っているんだな〜で良いと思います。

メソッド

これは、オブジェクトの振る舞いを表します。
先程のクラス名簿で考えると、実際の生徒が歩いたり、
授業中に寝たりみたいな振る舞いはメソッドに当たりますね。

クラスの実装

Rubyにおいて、クラス定義は、

class StudentMember
end

みたいな感じで定義します。

StudentMemberというのがクラス名です。
(先程のクラス名簿に寄せました、冗長ですね)

クラス名は、大文字で始めます。
キャメルケースとかパスカルケースで書くのが通例なので、
Student_Memberとかじゃなくて、
StudnetMemberですね。

StudentMemberだと冗長なので、
以下、Studentにします。

クラスをインスタンス化する場合は、

Student.new

でできます。

このnewメソッドを呼び出した際、
インスタンスの初期化に当たるので、
initializeメソッドが定義されている場合、
そこが呼ばれます。

class Student
  def initialize
    puts '今日から入学しました'
  end
end

Student.new
# 今日から入学しましたと表示される

initializeメソッドに引数をつけると、
newする際に、その引数を渡す必要があります。

class Student
  def initialize(name, attendance_number)
    puts "今日から入学しました、出席番号#{attendance_number}番の#{name}です"
  end
end

Student.new('斎藤太郎', 12)
# 今日から入学しました、出席番号12番の斎藤太郎です

インスタンスメソッド

インスタンスメソッドとは、クラス構文の内部で定義されたメソッドで、
インスタンスに対して、呼び出すメソッドです。

class Student
  def initialize(name, attendance_number)
    puts "今日から入学しました、出席番号#{attendance_number}番の#{name}です"
  end

  def walk
    puts '今日は1キロ歩いたぜ'
  end
end

student = Student.new('斎藤太郎', 12) 
# このstudent変数がStundentクラスのインスタンス。
# インスタンス自体は #<Student:0x00007fa3e211e620>みたいな感じ

student.walk

インスタンス変数

インスタンス変数は、クラス内部で使用する、
同じインスタンスの内部で共有される変数のことです。

Rubyでは@ 変数みたいな記法で定義します。

class Student
  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end
end

student1 = Student.new('斎藤太郎', 12)
student2 = Student.new('佐藤桃子', 24)
student1.introduce # 斎藤太郎と申します。出席番号12番です
student2.introduce # 佐藤桃子と申します。出席番号24番です

インスタンス変数の注意点としては、
作成または代入前にいきなり使用してもエラーにならず、nilを返すという点です。

そのため、バグを発見しにくくなるときがあります。

class Student
  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@nane}と申します。出席番号#{@attendance_number}番です" #typoしてみた
    puts @nana.nil? # いきなり使ってもnilと返るだけでエラーにならず
  end
end

student1 = Student.new('斎藤太郎', 12)
student1.introduce # と申します。出席番号12番です。
# あれ、名前が表示されていないぞ?

アクセサメソッド

また、インスタンス変数は、クラス外部からは参照できません。

class Student
  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end
end

student1 = Student.new('斎藤太郎', 12)
student1.introduce

# よっしゃさっき@nameに値を格納したはずだから取得してみよう
puts @name # nilが返るだけ
# とやっても取れない

ゲッター

インスタンス変数の値を取得した場合は、
それ用のメソッドを作る必要があります。

class Student
  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end

  def name # こいつ
    @name
  end
end

student1 = Student.new('斎藤太郎', 12)
puts student1.name

これのように値を取得するメソッドをゲッターメソッドといいます。

セッター

インスタンス変数の値を変更したい場合は、
=で終わるメソッドを定義して実現します。

class Student
  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end

  def name
    @name
  end

  def name=(val)
    @name = val
  end
end

student1 = Student.new('斎藤太郎', 12)
puts student1.name
student1.name = '仮面ライダー'
puts student1.name

これ紛らわしいですが、
student1.name = '仮面ライダー'の部分は、
代入ではなく、name=メソッドの呼び出しになっています。

これのように、値を書き込むメソッドをセッターメソッドといいます。

attr_accessor

上記のようなセッターやゲッターの記述は、
Rubyではattr_accessorという記法で省略することができます。

class Student
  attr_accessor :name

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end

  # attr_accessorで下記の2つのメソッドを定義する必要がなくなる
  # def name
  #   @name
  # end

  # def name=(val)
  #   @name = val
  # end
end

student1 = Student.new('斎藤太郎', 12)
puts student1.name
student1.name = '仮面ライダー'
puts student1.name

読み取りだけにしたい場合は、
attr_readerを、

書き込みだけにしたい場合は、
attr_writerを使うこともできます。

Macとマグカップの画像

クラスメソッド

上記は、クラス構文の内部で定義されるインスタンスメソッドが中心だったのに対し、
クラス自体に定義するクラスメソッドというものもあります。

記法としては、

class Student
  attr_accessor :name

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end

  def self.study
    puts "学生の本分は勉強にあります"
  end
end

Student.study # 学生の本分は勉強にあります

という記法と、

class Student
  attr_accessor :name

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号#{@attendance_number}番です"
  end

  # def self.study
  #   puts "学生の本分は勉強にあります"
  # end
  
  class << self
    def study
      puts "学生の本分は勉強にあります"
    end
  end
end

Student.study # 学生の本分は勉強にあります

という2つの記法があります。

クラスメソッド自体、インスタンスメソッドに比べると、
やや使用頻度が低いので、一概にどちらの記法が良いということはないと思いますが、
前者の方が比較的よく見かける気がします。

後者の記法は、クラスメソッドを大量に定義する場合は、
何度もselfを書く必要がなくなるので良いでしょう。

classにおける定数と変数

定数は、大文字で始まる、慣習的に大文字やアンダースコアで構成されるものですが、
変数と違い、定数はインスタンスメソッドやクラスメソッドなどの中からも、
参照することができます。

class Student
  attr_accessor :name
  PRICE = "定数の値段です100"
  price = "変数の値段です300"
  # ここは両方問題ない
  puts PRICE 
  puts price

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
    puts price #  undefined local variable or method `price' for #<Student:0x00007f909891c528> (NameError)になる
    puts PRICE # 問題なく出力される
  end
end

Student.new('斎藤', 1)

細かい変数と定数の違いは、
下記のドキュメントに詳しくあるので、
参考ください。

https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html

また下記の記事でも軽く触れています。

Ruby変数やすべてがオブジェクトについて
参考【Rubyすべてがオブジェクト】変数と真偽値/論理演算子/コメント

本記事は、Rubyの基礎文法である、変数や真偽値、論理演算子に触れると同時に、「すべてがオブジェクト」というRubyの特徴的な思想についても解説します。 この思想は、Rubyの文法の根幹になっているの ...

続きを見る

なお、定数は、クラスの外部から参照することもできます。

class Student
  attr_accessor :name
  PRICE = "定数の値段です100"

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end
end

Student.new('斎藤', 1)
# 定数はクラスの外部から取得することもできる
puts Student::PRICE

メソッドの可視性

さて、いろんなメソッドを見てみましたが、
これ以外にも、可視性という観点でメソッドを分類することができます。

すなわち、

ポイント

  • public
  • private
  • protected

のどれかという問題にあたります。
※ protectedは使用頻度が2つに比べてガクッと下がる上に、privateとの違いが、
ややこしいので本記事ではprotectedの解説は割愛します。

publicメソッド

publicメソッドは、クラスの外部からも呼び出せるもので、
何も指定しなかったら、デフォルトでこのpublicメソッドになります。

class Student
  def initialize(name, attendance_number)
    puts "今日から入学しました、出席番号#{attendance_number}番の#{name}です"
  end

  def walk
    puts '今日は1キロ歩いたぜ'
  end
end

student = Student.new('斎藤太郎', 12) 
# publicメソッドはクラスの外部からも呼び出せる
student.walk

privateメソッド

上記の、publicメソッドに対して、
privateメソッドとは、クラスの外部から呼び出せず、
内部でのみ使えるメソッドに該当します。

class Student
  def initialize(name, attendance_number)
    puts "今日から入学しました、出席番号#{attendance_number}番の#{name}です"
  end

  def run
    walk # ここはクラス内部だから呼び出せる
    puts '少し歩いたから走るか'
  end

  private # ここから下はprivateメソッドになる

  def walk
    puts '今日は1キロ歩いたぜ'
  end
end

student = Student.new('斎藤太郎', 12) 
student.run
# ここはクラス外部だからエラーになる
student.walk # private method `walk' called for #<Student:0x00007fdf9b91d570> (NoMethodError)となる

継承

継承とは、既にあるクラスの内容をもとにして、
別のクラスを作ることで、文字通り既存クラスを継承した、
新しいクラスを定義することです。

class Student
  attr_accessor :name

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号は#{@attendance_number}です"
  end
end

class LiteratureStudent < Student
  def initialize(name, attendance_number, favorite_book, favorite_author)
    # @name = name # これは親クラスにも存在している
    super(name, attendance_number)
    @favorite_book = favorite_book
    @favorite_author = favorite_author
  end

  def introduce
    super
    puts "愛読書は#{@favorite_book}で、好きな作家は#{@favorite_author}です"
  end
end

literature_student = LiteratureStudent.new('太宰', 5, '「心」', '夏目漱石')
puts literature_student.name # 親クラスでattr_accessorでnameが定義されているので、子クラスで定義しなくても呼び出せる
literature_student.introduce

上記の場合、LiteratureStudentというものが、
Studentクラスを継承した新しいクラスに該当します。

この場合、Studentクラスは、LiteratureStudentクラスのスーパークラス(親クラス)と言います。
逆にLiteratureStudentクラスは、Studentクラスのサブクラス(子クラス)と言います。

継承は、スーパークラスの機能を引き継ぐものであるため、
基本的には、サブクラスはスーパークラスの一種というように、
包含関係であることが理想です。

文学部の学生は、学生という概念の一種と言えそうなので、
とりあえず、継承関係にはありそうですね。

以前の記事で、
すべてがオブジェクトということについて解説したように、
Rubyの場合、継承関係の頂点にBasicObjectというものが存在します。

そのため、文字列とか数値とか当たり前に使っているものも、
実はこうしたものを継承したオブジェクトに過ぎないということがわかります。

ちなみに、class Userのようにただのクラス定義は、
デフォルトでObjectクラスを継承しています。

スーパークラスのメソッドを呼ぶ

先程の、継承したクラスをもう一度見てみます。

class Student
  attr_accessor :name

  def initialize(name, attendance_number)
    @name = name # 引数のnameを@nameというインスタンス変数に格納する
    @attendance_number = attendance_number
  end

  def introduce
    puts "#{@name}と申します。出席番号は#{@attendance_number}です"
  end
end

class LiteratureStudent < Student
  def initialize(name, attendance_number, favorite_book, favorite_author)
    # @name = name # これは親クラスにも存在している
    super(name, attendance_number)
    @favorite_book = favorite_book
    @favorite_author = favorite_author
  end

  def introduce # オーバーライド
    super
    puts "愛読書は#{@favorite_book}で、好きな作家は#{@favorite_author}です"
  end
end

literature_student = LiteratureStudent.new('太宰', 5, '「心」', '夏目漱石')
puts literature_student.name # 親クラスでattr_accessorでnameが定義されているので、子クラスで定義しなくても呼び出せる
literature_student.introduce

ここで、superという記法に注目します。
これで親クラスの同名メソッドを呼び出します。

なお、上記のコードでいうと、
introduceメソッドの内容が、
子クラスの場合で変わっていると思いますが、
このように同名メソッドを子クラスの方で上書きすることをオーバーライドと言います。

  • この記事を書いた人
  • 最新記事

reisuta

Webエンジニア | 20代中盤 | 大学時代はGmailすら知らないIT音痴でプログラミングとは無縁の生活を送る → 独学でプログラミングを学ぶ → Web系受託開発企業にエンジニアとして就職 → Web系自社サービス企業に転職 | 実務未経験の頃からVimを愛好しており、仕事でもプライベートでも開発はVimとTmuxを使っているので、VSCodeに疎いのが最近の悩み。何だかんだでやっぱりRubyが好き。

おすすめ記事はこちら

Vim/Neovimプラグイン 1

プラグインをどれだけ入れるかは、その人の思想なども関係するので、一概にこれがいいというのはないかもしれません。 プラグインを全く入れない人もいれば、100個以上入れる人もいます。 ただそれでも、これだ ...

VimとNeovimの比較 2

本記事では、VimとNeovimの違いについて、解説します。 VimとNeovimの違いについては、普段頻繁にVimなどを使う方でなければ、正直、あまり気にしなくてもいいかなと思います。 ただ、Vim ...

Ruby変数やすべてがオブジェクトについて 3

本記事は、Rubyの基礎文法である、変数や真偽値、論理演算子に触れると同時に、「すべてがオブジェクト」というRubyの特徴的な思想についても解説します。 この思想は、Rubyの文法の根幹になっているの ...

4

エンジニアにおすすめの技術書 書籍学習は、エンジニアの嗜みみたいなところがありますが、 良書というものは、意外とそこまで多くもありません。 そこで本記事では「技術書マニアの筆者が厳選した技術書20選」 ...

5

エンジニアになるには? プログラミングは、専門性が高く自分一人で勉強するのが大変に感じることも多いですよね。 そこで本記事では「おすすめのプログラミングスクール5選」を特徴と、現役エンジニア目線で優れ ...

-バックエンド