🦓

[Ruby]クラスとは

2023/06/20に公開

はじめに

Rubyのクラスについて見ていきます。

Rubyの特徴

Rubyの世界では全ての「もの」はオブジェクトとなります。
プログラムの中でデータを持ったり、操作したりする対象です。

クラスとは

クラスはオブジェクトの設計図となるものです。
クラスは属性(データ)とメソッドを持ち、そのクラスを基にして複数のインスタンス(オブジェクト)を生成することができます。

オブジェクトとクラスの関係

オブジェクトはクラスに属しています。
クラスに属するオブジェクトは、そのクラスの「インスタンス」であります。
オブジェクトは独自の状態とメソッドを持ち、クラスの特徴を具体化したものであります。
複数のオブジェクトが同じクラスから生成されることができますが、それぞれのオブジェクトは独自の状態を持ちます。

オブジェクト名 クラス名
1,2,100 整数オブジェクト Integer
"hello","コーヒー" 文字列オブジェクト String
[1,2,3],["コーヒー", "紅茶", "カフェラテ"] 配列オブジェクト Array

classメソッド

オブジェクトがどのクラスに属しているかは、classメソッドで調べることができます。
また、オブジェクトは、所属するクラスが用意しているメソッドを使うことができます。

p 1.class # Integer
p "Hello".class # String
p "".class # String

p 1.even? # false
p 2.even? # true

オブジェクトを作る

クラスを基にしてインスタンス(オブジェクト)を生成するには、クラス名の後に .new メソッドを呼び出します。

p [1,2,3] # Arrayオブジェクト
p Array.new # []

クラスを作る

新しいクラスを作ることで、新しい特徴を持ったオブジェクトを作ることができます。

class Person
end
person = Person.new
puts person.class # Person
# 一行で書く
p Person.new.clsass

作ったPersonクラスのオブジェクトにpersonという名前をつけています。
その二つは、別のものであることを気を付けましょう。

Objectクラス

Objectクラスはすべてのクラスの元になるクラスを指します。
1.class.superclass.superclassとメソッドチェーンを使ったり、Integer.ancestorsによって確認できます。

クラス名の規則

1. クラス名は大文字で始めます: クラス名は大文字で始まる必要があります。これは、Rubyにおいて大文字で始まる識別子は通常、クラスやモジュールの名前として使用されるためです。

2. クラス名はキャメルケースを使用します: 複数の単語を結合する場合は、キャメルケース(単語の頭文字を大文字にする)を使用します。例えば、PersonEmployeeRecordMyCustomClassなどが有効なクラス名です。

メソッドを作る

クラスにメソッドを定義すると、そのクラスに属するオブジェクトたちはそのメソッドを呼び出すことができます。

class MyClass
  def greet(name)
    puts "Hello, #{name}!"
  end
end

my_object = MyClass.new
my_object.greet("Alice")  # Output: Hello, Alice!

my_objectオブジェクトがgreetメソッドを呼び出しています。

レシーバ

my_objectオブジェクトをレシーバとも言います。
my_object.greetというコードは、my_objectオブジェクトにgreetメッセージを送信し、my_objectオブジェクトがそのメッセージを受け取り、greetメソッドを実行します。

レシーバ(Receiver)は、メッセージを受け取る対象のオブジェクトを指します。メッセージを送信するとき、そのメッセージはレシーバに対して送られます。レシーバはメッセージを受け取り、それに対応する処理を実行します。

methodsメソッド

methodsメソッドを使うと、レシーバであるオブジェクトで呼び出せるメソッドを一覧表示することができます。

p 1.methods

[:anybits?, :nobits?, :downto, :times, :pred, :pow, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :next, :-@, :digits, :[], :magnitude, :zero?, :integer?, :bit_length, :upto, :even?, :odd?, :%, :chr, :&, :*, :+, :inspect, :-, :/, :size, :succ, :<, :>, :ord, :to_int, :to_s, :to_i, :to_f, :to_r, :div, :divmod, :fdiv, :^, :coerce, :numerator, :denominator, :modulo, :remainder, :gcd, :lcm, :gcdlcm, :abs, :floor, :ceil, :round, :truncate, :rationalize, :|, :~, :allbits?, :imag, :dup, :abs2, :phase, :+@, :to_c, :angle, :conjugate, :conj, :negative?, :step, :positive?, :real?, :infinite?, :finite?, :eql?, :singleton_method_added, :quo, :clone, :i, :arg, :nonzero?, :rectangular, :rect, :polar, :real, :imaginary, :between?, :clamp, :singleton_class, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :display, :hash, :public_send, :class, :tap, :yield_self, :then, :frozen?, :extend, :method, :public_method, :singleton_method, :define_singleton_method, :=~, :!~, :nil?, :respond_to?, :freeze, :object_id, :send, :to_enum, :enum_for, :__send__, :!, :instance_eval, :instance_exec, :!=, :equal?, :__id__]

オブジェクトにデータを持たせる

インスタンス変数

class Drink
    def order(item)
        puts "#{item}をください"
        name = item
    end

    def reorder
        puts "#{item}をもう一杯ください"
    end
end

drink = Drink.new
drink.order("カフェラテ") # カフェラテをください
drink.order # カフェラテをください
drink.reorder 
`reorder': undefined local variable or method `item' for #<Drink:0x00000001076fb5a0> (NameError)

2回目のdrink.orderでは引数を設定していませんが、オブジェクト内のインスタンス変数は保存されるため、1回目のdrink.orderの引数『カフェラテ』が自動的に設定されます。

nameはローカル変数なので、reorderメソッドの中では使うことができません。
より広いスコープを持つ「インスタンス」変数を使ってみましょう。

インスタンス変数(Instance Variables)は、Rubyにおいてオブジェクト内部でのデータの保存や共有に使用される変数です。インスタンス変数は、そのオブジェクトのインスタンスごとに異なる値を持つことができます。

インスタンス変数は、@を接頭辞として使い、変数名を表します。

class Drink
    def order(item)
        puts "#{item}をください"
        @name = item
    end

    def reorder
        puts "#{@name}をもう一杯ください"
    end
end

drink = Drink.new
drink.order("カフェラテ") # カフェラテをください
drink.reorder # カフェラテをもう一杯ください

# オブジェクトごとにデータを持つことができる
drink1 = Drink.new
drink1.order("モカ") # モカをください

インスタンス変数は、オブジェクト内の他のメソッドでも使用でき、そのオブジェクトの状態を保持したり、異なるメソッド間でデータを共有するために利用されます。インスタンス変数は通常、オブジェクトの内部のみでアクセス可能であり、外部から直接アクセスすることはできません。しかし、アクセサメソッド(attr_reader, attr_writer, attr_accessor)を使用することで、外部からもアクセスできるようになります。

instance_variableメソッド

instance_variableメソッドは、指定したオブジェクトが持っている全てのインスタンス変数を返します。メソッドの呼び出し元となるオブジェクトのインスタンス変数の変数名を取得する際に利用されます。

class Drink
    def order(item)
        @name = item
    end

end

drink = Drink.new
drink.order("カフェラテ")
p drink.instance_variables # [:@name]

initializeメソッド

インスタンス変数により便利、オブジェクトが作られる時にデータを持たせる仕組みinitializeメソッドがあります。
このメソッドは、オブジェクトの初期化やインスタンス変数の設定など、インスタンスが生成された直後に行いたい初期化処理を記述するために使用されます。

class Person
  attr_accessor :name, :age
  
  def initialize(name, age)
    @name = name
    @age = age
  end
end

person = Person.new("Alice", 25)
puts person.name  # 出力: Alice
puts person.age   # 出力: 25

上記の例では、Personクラス内のinitializeメソッドが引数nameageを受け取り、それぞれの値を@name@ageに代入しています。Person.new("Alice", 25)によってオブジェクトが作成され、nameageの値が初期化されます。
initializeメソッドは必ずしも定義する必要はありません。クラス内にinitializeメソッドが定義されていない場合、デフォルトのコンストラクタが使用され、特別な初期化処理は行われません。

クラスメソッド

ここまでは、クラスを使ってオブジェクトを作り、オブジェクトを使ってメソッドを呼ぶだせることを見てきました。オブジェクトを作らずに、クラスを使って呼び出せる「クラスメソッド」もあります。

メソッド名の前にselfを付けることで、そのメソッドがクラスメソッドであることを定義します。

def self.メソッド名
end

class MyClass
  def self.my_class_method
    puts "This is a class method."
  end
end

MyClass.my_class_method  # Output: This is a class method.
名前 定義 呼び出し方 レシーバ
インスタンスメソッド def メソッド名 インスタンス.メソッド インスタンス
クラスメソッド def self.メソッド名 クラス.メソッド クラス

class << self

通常、クラスメソッドはメソッド名の前にselfを付けて定義しますが、class << self構文を使用することで、複数のクラスメソッドをまとめて定義することができます。

以下にclass << self構文の使用例を示します:

class MyClass
  class << self
    def method1
      puts "This is method 1."
    end

    def method2
      puts "This is method 2."
    end
  end
end

MyClass.method1  # Output: This is method 1.
MyClass.method2  # Output: This is method 2.

method1method2はいずれもクラスメソッドとして定義されており、クラス自体に対して呼び出すことができます。

class << self構文を使用することで、クラスメソッドを定義する範囲を明示的に指定することができます。この構文を使用することで、インスタンスメソッドとクラスメソッドを同時に定義することも可能です。

注意点として、class << self構文内では、インスタンス変数には直接アクセスすることができません。クラス変数やクラス定数にはアクセスすることができますが、インスタンス変数にアクセスする場合は、明示的にselfを付けたクラスメソッドを定義する必要があります。

クラス変数

クラス変数(Class Variables)は、クラスとそのサブクラス間で共有される変数です。クラス変数は、同じクラスの異なるインスタンス間で共有される値を保持するために使用されます。

クラス変数は@@で始まる識別子で表されます。クラスの定義内でクラス変数に値を代入すると、そのクラスとそのサブクラスのすべてのインスタンスからアクセスできるようになります。

class Car
  attr_accessor :color  # インスタンス変数のゲッターとセッターを自動生成

  def initialize(color)
    @color = color
  end

  def self.total_cars
    @@total_cars ||= 0
  end

  def self.increase_total_cars
    @@total_cars ||= 0
    @@total_cars += 1
  end
end

car1 = Car.new("Red")
car2 = Car.new("Blue")

puts car1.color  # Output: Red
puts car2.color  # Output: Blue

Car.increase_total_cars
Car.increase_total_cars

puts Car.total_cars  # Output: 2

Carクラスはインスタンス変数@colorを持ち、それぞれのインスタンスに異なる色が設定されます。attr_accessorを使用することで、colorのゲッターとセッターが自動的に生成されます。

total_carsメソッドはクラス変数@@total_carsの値を返します。初期値は0となっており、クラスメソッドincrease_total_carsを呼び出すことでカウントが増えます。

car1car2のインスタンスを作成し、それぞれの色を表示しています。また、Car.total_carsで作成された車の総数を表示しています。

インスタンス変数@colorはインスタンスごとに異なる値を持ちますが、クラス変数@@total_carsは全てのインスタンス間で共有される値となります。クラスメソッドはクラス自体に対して呼び出されるため、インスタンスの作成や操作とは独立しています。

継承

クラスの継承(Inheritance)は、Rubyにおいてクラス間での特性や機能の共有を実現するための仕組みです。継承によって、既存のクラス(スーパークラスまたは親クラス)の特性を引き継ぎながら、新しいクラス(サブクラスまたは子クラス)を作成することができます。

サブクラスは、スーパークラスの特性やメソッドを継承するだけでなく、追加の特性やメソッドを定義することもできます。これにより、コードの再利用や階層的な構造の表現が容易になります。

class Vehicle
  attr_accessor :brand, :model

  def initialize(brand, model)
    @brand = brand
    @model = model
  end

  def honk
    puts "Beep beep!"
  end
end

class Car < Vehicle
  attr_accessor :color

  def initialize(brand, model, color)
    super(brand, model)
    @color = color
  end

  def honk
    puts "Honk honk!"
  end

  def drive
    puts "The #{color} #{brand} #{model} is driving."
  end
end

class Motorcycle < Vehicle
  def honk
    puts "Vroom vroom!"
  end

  def wheelie
    puts "The motorcycle is popping a wheelie!"
  end
end

car = Car.new("Toyota", "Camry", "Red")
car.honk  # Output: Honk honk!
car.drive  # Output: The Red Toyota Camry is driving.

motorcycle = Motorcycle.new("Harley-Davidson", "Sportster")
motorcycle.honk  # Output: Vroom vroom!
motorcycle.wheelie  # Output: The motorcycle is popping a wheelie!

親のクラスで同名のメソッドがあるときは、最初に該当したメソッドを呼び出します。
メソッドの中でsuperをかくと親クラスの同名メソッドを呼び出すことができます。

上記の例では、Vehicleクラスを定義し、そのサブクラスとしてCarクラスとMotorcycleクラスを作成しています。

Vehicleクラスは車両の基本的な特性を持ち、brand(ブランド)とmodel(モデル)のインスタンス変数を持ちます。また、honkメソッドはクラクションの音を出力します。

CarクラスはVehicleクラスを継承しており、追加の特性としてcolor(色)のインスタンス変数を持ちます。initializeメソッドでは、親クラスのinitializeメソッドを呼び出すためにsuperキーワードを使用しています。honkメソッドをオーバーライドして、異なるクラクションの音を出力するようにしています。また、driveメソッドは車が走行することを表示します。

MotorcycleクラスもVehicleクラスを継承しており、honkメソッドをオーバーライドして独自のクラクションの音を出力します。wheelieメソッドはバイクがホイールを持ち上げることを表示します。

例では、CarクラスとMotorcycleクラスのインスタンスを作成し、それぞれのメソッドを呼び出しています。carオブジェクトはCarクラスの特性とメソッドを継承し、motorcycleオブジェクトはMotorcycleクラスの特性とメソッドを継承しています。

これにより、carオブジェクトはVehicleクラスとCarクラスの特性を持ち、motorcycleオブジェクトはVehicleクラスとMotorcycleクラスの特性を持ちます。それぞれのオブジェクトは継承されたメソッドや独自のメソッドを使用することができます。

メソッドの呼び出しを制限する

privatepublicメソッド

publicメソッド: publicキーワードで定義されたメソッドは、どのコンテキストからでもアクセス可能です。これがデフォルトのアクセスレベルです。クラス内部から、またはクラスのインスタンスから呼び出すことができます。また、サブクラスや他のオブジェクトからも呼び出すことができます。

privateメソッド: privateキーワードで定義されたメソッドは、同じクラス内でのみアクセス可能です。つまり、外部からの直接的な呼び出しはできず、クラス内部の別のメソッドからのみ呼び出すことができます。一般的には、内部の補助的なメソッドや処理の詳細を隠すために使用されます。

class BankAccount
  attr_reader :balance

  def initialize(initial_balance)
    @balance = initial_balance
  end

  def deposit(amount)
    update_balance(amount)
    puts "Deposited #{amount} into the account. New balance: #{@balance}"
  end

  def withdraw(amount)
    if enough_balance?(amount)
      update_balance(-amount)
      puts "Withdrawn #{amount} from the account. New balance: #{@balance}"
    else
      puts "Insufficient balance to withdraw #{amount}. Current balance: #{@balance}"
    end
  end

  private

  def enough_balance?(amount)
    @balance >= amount
  end

  def update_balance(amount)
    @balance += amount
  end
end

account = BankAccount.new(1000)
account.deposit(500)   # Output: Deposited 500 into the account. New balance: 1500
account.withdraw(2000) # Output: Insufficient balance to withdraw 2000. Current balance: 1500
account.withdraw(800)  # Output: Withdrawn 800 from the account. New balance: 700

account.enough_balance?(100) # NoMethodError: private method `enough_balance?' called for #<BankAccount:0x0000000000000000>

上記の例では、BankAccountクラスを定義しています。このクラスは銀行口座を表し、balanceというインスタンス変数を持ちます。

depositメソッドは預金を行い、withdrawメソッドは引き出しを行います。enough_balance?メソッドは引き出し時に残高が十分かどうかを判定するために使用されます。

depositメソッドとwithdrawメソッドはpublicで定義されており、外部から直接呼び出すことができます。しかし、enough_balance?メソッドとupdate_balanceメソッドはprivateで定義されているため、クラス内部からのみアクセス可能です。

accountオブジェクトを作成し、depositメソッドとwithdrawメソッドを呼び出しています。それぞれのメソッドは、公開されているので直接呼び出すことができます。

しかし、account.enough_balance?(100)のようにenough_balance?メソッドを直接呼び出そうとすると、NoMethodErrorが発生します。これは、enough_balance?メソッドがprivateであるため、外部からの呼び出しを許可していないからです。

privateメソッドを使うことで、銀行口座の残高の更新や残高判定などの内部的な処理を隠蔽し、クラスの利用者に必要なインタフェースのみを公開することができます。これにより、クラスの安全性と保守性を向上させることができます。

終わりに

「クラス」と「オブジェクト」をまとめてみました。
都度振り返って復習を行い、理解を深めていきましょう。

https://pikawaka.com/ruby/class

Discussion