Ruby プログラミング

[Ruby] オブジェクト指向プログラミング1(クラスの基本)

class

こんにちは!じゃいごテックのあつしです。

Rubyでプログラミングの基礎を学習した後は、アプリ開発などに挑戦するかと思いますが、その際、オブジェクト指向プログラミングが必要になってきます。
最初は少し難しく感じるかもしれませんが、慣れてくると(ある程度)自然にオブジェクト指向で考えて書けるようになってきます。
Rubyもオブジェクト指向プログラミング言語ですので、私が説明できる範囲の基本的なことを紹介していきたいと思います。

オブジェクト指向プログラミングとは

オブジェクト指向プログラミングとは、プログラム全体を「ある役割を持ったオブジェクト(モノ)」ごとに分割して、モノに命令を与えたり、モノとモノを相互に作用させることによって、プログラムを構築していく手法です。
モノの設計図のことをクラスと言い、クラスから生み出された実体(モノ)をインスタンスと言います。

クラス

Rubyでは全てのオブジェクトは必ず何らかのクラスに属しています。
クラスはプロパティ(属性・データ)とメソッド(命令)をまとめた設計図のようなもので、その設計図によって実体化されたオブジェクト(モノ)に命令することでプログラムを進めていきます。

# 123 はIntegerクラス
123.class
# > Integer

# Integerクラスに定義されているgcdメソッドを呼び出す
# gcdメソッドは自身と引数の最大公約数を返す
p 123.gcd(456)
# > 3
 
# "abc" はStringクラス
"abc".class
# > String

# Stringクラスに定義されているupcaseメソッドを呼び出す
# upcaseメソッドは自身を大文字に変換した文字列を返す
p "abc".upcase
# > "ABC"

# [1, 2, 3, "a", "b", "c"] はArrayクラス
# classメソッドは自身のクラスを返す。
[1, 2, 3, "a", "b", "c"].class
# > Array

# Arrayクラスに定義されているlengthメソッドを呼び出す
# lengthメソッドは自身の要素数を返す
[1, 2, 3, "a", "b", "c"].length
# > 6

クラスの定義

クラス名はアッパーキャメルケース(UpperCamelCase)キャメルケース(CamelCase)で命名し、class クラス名 ~ end の間に処理を記述していきます。

class クラス名


end

試しにAppleクラスを作ってみます

# Appleクラスを定義(中身は空っぽ)
class Apple
end

インスタンス

インスタンスとは、設計図であるクラスを元にして実体化させたモノです。
インスタンスを生成することで、クラスで設計されたプロパティやメソッドが使えるようになります。

クラスのインスタンス化

クラス名.new(パラメータ)

クラスはインスタンス化することにより、定義しておいたプロパティやメソッドが利用できるようになります。

class Apple
end

# インスタンス化して 変数 ringo に格納
ringo = Apple.new()

# ringo は Appleクラス のインスタンス
p ringo
# > #<Apple:0x00007fffc5c5ff68>
p ringo.class
# > Apple

Appleクラスの中は空っぽなので何も命令することが出来ませんが、とりあえずインスタンス化することは出来ました。

※ Appleクラスは空のはずなのにclassメソッドが使える理由は次の記事で説明します。

インスタンス変数

インスタンス変数とはオブジェクト毎に個別で持つことが出来る変数で、@nameのように、変数名の先頭に@を付けます。

インスタンス変数の保存・更新

Appleクラスにデータを持たせてみましょう。
インスタンスに値を保存しておくためのインスタンス変数を定義して、値を格納するメソッドを作ります。
例としてインスタンス変数@nameを定義して、名前を保存出来るようにします。

class Apple
  # セッター
  def name=(name)
    @name = name
  end
end

# Appleクラスのインスタンスを生成する
ringo = Apple.new
p ringo
# > #<Apple:0x00007fffeb78fb48>

# インスタンス変数 @name に "ohrin" を保存する
ringo.name = "ohrin"
p ringo
# > #<Apple:0x00007fffd6097968 @name="ohrin">

# インスタンス変数 @name を "toki" に変更して保存する
ringo.name = "toki"
p ringo
# > #<Apple:0x00007fffd6097968 @name="toki">

このようにインスタンス変数を保存または更新するメソッドをセッターと言います。

インスタンス変数の参照

現在の状態ではAppleクラスのインスタンス変数を直接参照することは出来ません。

class Apple
  # セッター
  def name=(name)
    @name = name
  end
end

# Appleクラスのインスタンスを生成する
ringo = Apple.new
p ringo
# > #<Apple:0x00007fffeb78fb48>

# インスタンス変数 @name に "ohrin" を保存する
ringo.name = "ohrin"

# インスタンス変数 @name を参照できない
p ringo.name
# > undefined method `name'
# > for #<Apple:0x00007fffd84ff030 @name="ohrin"> (NoMethodError)

インスタンス変数を参照するメソッドを作ってみましょう。

class Apple
  # セッター
  def name=(name)
    @name = name
  end

  # ゲッター
  def name
    @name
  end
end

# Appleクラスのインスタンスを生成する
ringo = Apple.new
p ringo
# > #<Apple:0x00007fffeb78fb48>

# インスタンス変数 @name に "ohrin" を保存する
ringo.name = "ohrin"

# インスタンス変数 @name を参照する
p ringo.name
# > "ohrin"

このようにインスタンス変数を参照するメソッドをゲッターと言います。

アクセスメソッド

アクセスメソッドを使うと、ゲッターメソッド・セッターメソッドを記述しなくてもインスタンス変数の保存や参照を行うことが出来るようになります。

ゲッター(外部から参照が可能) attr_reader :変数名1, :変数名2, ...
セッター(外部から更新が可能) attr_writer :変数名1, :変数名2, ...
ゲッター&セッター(外部から参照と更新が可能) attr_accessor :変数名1, 変数名2, ...

@nameを外部から参照と更新できるようにする場合はattr_accessorメソッドを使います。

class Apple
  # @name は外部から参照と更新を許可する
  attr_accessor :name
end

# Appleクラスのインスタンスを生成する
ringo = Apple.new
p ringo
# > #<Apple:0x00007fffeb78fb48>

# インスタンス変数 @name に "ohrin" を保存する
ringo.name = "ohrin"

# インスタンス変数 @name を参照する
p ringo.name
# > "ohrin"

initializeメソッド

def initialize(引数1, 引数2, ..)
.
.
end

initializeメソッドはオブジェクトがインスタンス化されたときに自動的に呼び出されるメソッドで、引数を与えて実行することが出来ます。
オブジェクト生成時に必ず実行したい処理(インスタンス変数の初期化など)を記述します。

Appleクラスのインスタンス変数を増やして、複数のApplesクラスのオブジェクトをインスタンス化して配列に格納してみます。

class Apple
  # @name と @color は外部からは参照を許可する
  attr_reader :name, :color
  # @sweetness と @sour は外部から参照と更新を許可する
  attr_accessor :sweetness, :sour

  # インスタンス化された時に引数の値をインスタンス変数に代入する
  def initialize(name, color, sweetness, sour)
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "red", 2, 3]]

# apple_list のデータを使ってAppleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

apples.each { |apple| p apple }
# > #<Apple:0x00007ffff365b378 @name="トキ", @color="yellow", @sweetness=5, @sour=2>
# > #<Apple:0x00007ffff365af40 @name="フジ", @color="red", @sweetness=3, @sour=3>
# > #<Apple:0x00007ffff365a900 @name="王林", @color="green", @sweetness=5, @sour=1>
# > #<Apple:0x00007ffff3658ce0 @name="紅玉", @color="red", @sweetness=1, @sour=5>
# > #<Apple:0x00007ffff3658bf0 @name="陸奥", @color="red", @sweetness=2, @sour=3>

インスタンスメソッド

class クラス名
def メソッド名(引数1, 引数2, ..)

    .
    .
  end
end

クラス内に記述されたメソッドは、そのクラスのインスタンスが呼び出すことが出来ます。
そのようなメソッドをインスタンスメソッドと言います。

Appleクラスに、りんごの情報を出力するinformationメソッドを定義して呼び出してみます。

class Apple
  attr_reader :name, :color
  attr_accessor :sweetness, :sour

  def initialize(name, color, sweetness, sour)
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end

  # りんごの情報を出力するインスタンスメソッド
  def information
    "品種名:#{@name.ljust(4, " ")} 色:#{jcolor}  " <<
    "甘さ:#{@sweetness}  酸っぱさ:#{@sour}"
  end

  # 英語の色を日本語の色に変換するインスタンスメソッド
  def jcolor
    case @color
    when "red"
      "赤"
    when "green"
      "緑"
    when "yellow"
      "黄"
    else
      "他"
    end
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "red", 2, 3]]

# Appleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

# 各りんごの情報を表示する informationメソッド を呼び出す
apples.each { |apple| puts apple.information }
# > 品種名:トキ   色:黄 甘さ:5 酸っぱさ:2
# > 品種名:フジ   色:赤 甘さ:3 酸っぱさ:3
# > 品種名:王林   色:緑 甘さ:5 酸っぱさ:1
# > 品種名:紅玉   色:赤 甘さ:1 酸っぱさ:5
# > 品種名:陸奥   色:赤 甘さ:2 酸っぱさ:3

# 配列1番目(トキ)の色を参照する
p apples[0].color
# > "yellow"
p apples[0].jcolor
# > "黄"

privateメソッド

クラス外から呼び出されたくないメソッドを定義したい場合はprivateメソッドを使用します。
jcolorメソッドを外部から呼び出されたくない場合、以下の様に記述します。

class Apple
  attr_reader :name, :color
  attr_accessor :sweetness, :sour

  def initialize(name, color, sweetness, sour)
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end

  # りんごの情報を出力するインスタンスメソッド
  def information
    "品種名:#{@name.ljust(4, " ")}  色:#{jcolor} " <<
    "甘さ:#{@sweetness}  酸っぱさ:#{@sour}"
  end

  # private 以降のメソッドは外部から呼び出せない
  private

  # 英語の色を日本語の色に変換するインスタンスメソッド
  def jcolor
    case @color
    when "red"
      "赤"
    when "green"
      "緑"
    when "yellow"
      "黄"
    else
      "他"
    end
  end

  def example_method1
    "example1"
  end

  def example_method2
    "example2"
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "red", 2, 3]]

# Appleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

# private 以降の行で定義したメソッドは外部から呼び出すことが出来ない
p apples[0].jcolor
# > private method `jcolor' called
# > for #<Apple:0x00007fffc4ccf648> (NoMethodError)
p apples[0].example_method1
# > private method `example_method1' called
# > for #<Apple:0x00007fffe0146ee0> (NoMethodError)
p apples[0].example_method2
# > private method `example_method2' called
# > for #<Apple:0x00007fffd65371d0> (NoMethodError)

クラス変数

クラスのインスタンス全てで共有する変数をクラス変数と言います。
@@変数名で定義し、インスタンスメソッドなどで利用することが出来ます。

class Apple
  attr_reader :name, :color
  attr_accessor :sweetness, :sour

  # クラス変数で通し番号を管理する
  @@counter = 1

  def initialize(name, color, sweetness, sour)
    # インスタンスに通し番号を振る
    @id = @@counter
    # クラス変数 @@counter をインクリメントする
    @@counter += 1
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end

  def information
    "品種番号:#{"%2d" % @id}  品種名:#{@name.ljust(4, " ")}  色:#{jcolor}  " <<
    "甘さ:#{@sweetness}  酸っぱさ:#{@sour}"
  end

  private

  def jcolor
    case @color
    when "red"
      "赤"
    when "green"
      "緑"
    when "yellow"
      "黄"
    else
      "他"
    end
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "red", 2, 3]]

# Appleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

# 品種番号 @id も含めたりんごの情報を表示する
apples.each { |apple| puts apple.information }
# > 品種番号: 1  品種名:トキ    色:黄  甘さ:5  酸っぱさ:2
# > 品種番号: 2  品種名:フジ    色:赤  甘さ:3  酸っぱさ:3
# > 品種番号: 3  品種名:王林    色:緑  甘さ:5  酸っぱさ:1
# > 品種番号: 4  品種名:紅玉    色:赤  甘さ:1  酸っぱさ:5
# > 品種番号: 5  品種名:陸奥    色:赤  甘さ:2  酸っぱさ:3

定数

クラス内で定数を定義することも出来ます。
アルファベット大文字で始まる定数名を定義して、変更することが出来ない値を格納する事ができ、外部からはクラス名::定数名で参照することができます。

今回はjcolorメソッドを削除して、英語と日本語の色の対応JCOLORを定数にします。
※ 今までは色が"other"に該当する品種がありませんでしたが、陸奥は赤も黄色もあるので"other"に変更します。

class Apple
  attr_reader :name, :color
  attr_accessor :sweetness, :sour

  # 英語と日本語の対応を定数にする
  JCOLOR = { "red" => "赤",
             "green" => "緑",
             "yellow" => "黄",
             "other" => "他" }
  # クラス変数で通し番号を管理する
  @@counter = 1

  def initialize(name, color, sweetness, sour)
    @id = @@counter
    @@counter += 1
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end

  def information
    "品種番号:#{"%2d" % @id}  品種名:#{@name.ljust(4, " ")}  色:#{JCOLOR[@color]}  " <<
    "甘さ:#{@sweetness}  酸っぱさ:#{@sour}"
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "other", 2, 3]]

# Appleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

# りんごの情報を表示する
apples.each { |apple| puts apple.information }
# > 品種番号: 1  品種名:トキ    色:黄  甘さ:5  酸っぱさ:2
# > 品種番号: 2  品種名:フジ    色:赤  甘さ:3  酸っぱさ:3
# > 品種番号: 3  品種名:王林    色:緑  甘さ:5  酸っぱさ:1
# > 品種番号: 4  品種名:紅玉    色:赤  甘さ:1  酸っぱさ:5
# > 品種番号: 5  品種名:陸奥    色:他  甘さ:2  酸っぱさ:3

# Appleクラスの 定数JCOLOR を参照
p Apple::JCOLOR
# > {"red"=>"赤", "green"=>"緑", "yellow"=>"黄", "other"=>"他"}

# 標準モジュールMathクラスの 定数PI を参照
p Math::PI
# > 3.141592653589793

特異メソッド(クラスメソッド)

class クラス名
  def self.メソッド名
  .
  .
  End
end

又は

class クラス名
  def クラス名.メソッド名
  .
  .
  end
end

クラス直下で頭にselfキーワード(またはクラス名)を付けてメソッドを定義することでクラスにメソッドを紐づけることができます。このようなメソッドを特異メソッドと言いい、インスタンスや外部から呼び出すことができます。

なお、Rubyリファレンスマニュアルによると「特異メソッドは他のオブジェクト指向システムにおけるクラスメソッドの働きをする」とのことでしたので、普段は特異メソッドのことをクラスメソッドと呼ぶことにします。

Appleクラスにクラス変数@@counterを参照するクラスメソッドget_counterを実装してみる。

class Apple
  attr_reader :name, :color
  attr_accessor :sweetness, :sour

  # 英語と日本語の対応を定数にする
  JCOLOR = { "red" => "赤",
             "green" => "緑",
             "yellow" => "黄",
             "other" => "他" }
  @@counter = 1

  # @@counter を参照するクラスメソッド
  def self.get_counter
    @@counter
  end

  def initialize(name, color, sweetness, sour)
    @id = @@counter
    @@counter += 1
    @name = name
    @color = color
    @sweetness = sweetness
    @sour = sour
  end

  def information
    "品種番号:#{"%2d" % @id}  品種名:#{@name.ljust(4, " ")}  色:#{JCOLOR[@color]}  " <<
    "甘さ:#{@sweetness}  酸っぱさ:#{@sour}"
  end
end

apple_list = [["トキ", "yellow", 5, 2],
              ["フジ", "red", 3, 3],
              ["王林", "green", 5, 1],
              ["紅玉", "red", 1, 5],
              ["陸奥", "other", 2, 3]]

# Appleクラスのインスタンスを生成する
apples = apple_list.map do |name, color, sweetness, sour|
  Apple.new(name, color, sweetness, sour)
end

# クラス変数 @@counter を出力する
p Apple.get_counter
# > 6

今回のまとめ

今回はオブジェクト指向プログラミングの概念とクラスの基本をざっくりご紹介しました。

  • オブジェクト指向プログラミング(OOP)の概念
  • クラスの定義
  • インスタンスの生成
  • インスタンス変数
  • アクセスメソッド(セッター・ゲッター)
  • initializeメソッド
  • インスタンスメソッド
  • privateメソッド
  • クラス変数・定数
  • 特異メソッド

Rubyの学習をしていると、いつかオブジェクト指向プログラミングにぶつかりますので、少しづつ慣れていきたいですね。
なお、マッキントッシュ (Macintosh)の由来となった、McIntoshの日本語品種名は旭(あさひ)です。(リンゴ小ネタ)

【PR】Ruby学習でお世話になった本




【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-Ruby, プログラミング
-,

© 2024 じゃいごテック