オブジェクト指向
Ruby
クラス・継承・モジュール・Mixin
クラスの定義
attr_accessor・initialize・可視性
class BankAccount
# アクセサの定義
attr_reader :owner # getter のみ
attr_writer :email # setter のみ
attr_accessor :balance # getter + setter
# クラス変数・クラスインスタンス変数
@@count = 0 # クラス変数(継承で共有)
@interest_rate = 0.02 # クラスインスタンス変数(継承で独立)
def initialize(owner, balance = 0)
@owner = owner
@balance = balance.to_f
@@count += 1
end
# インスタンスメソッド
def deposit(amount)
@balance += amount
self # メソッドチェーン用
end
# クラスメソッド
def self.count = @@count
def self.interest_rate = @interest_rate
# メソッドの可視性
def public_method = '誰でも呼べる'
protected
def protected_method = '同じクラス・サブクラスから呼べる'
private
def private_method = 'このクラスのインスタンスからのみ'
def validate_amount(amount)
raise ArgumentError, '金額は正の数' unless amount.positive?
end
# private は self. でも呼べない(Ruby 2.7 以前)
# Ruby 3.0+ は private メソッドに self. を使えない
end
acc = BankAccount.new('Alice', 1000)
acc.deposit(500).deposit(200) # メソッドチェーン
puts BankAccount.count # 1継承とモジュール
super・オーバーライド・include・extend・prepend
# 継承(単一継承)
class Animal
attr_reader :name
def initialize(name) = (@name = name)
def speak = raise NotImplementedError, "#{self.class} must implement speak"
def to_s = "#{self.class.name}(#{name})"
end
class Dog < Animal
def speak = 'ワン!'
def fetch(item)
"#{name}が#{item}を取ってきた!"
end
end
class GoldenRetriever < Dog
def speak
super + ' ワン!' # 親のメソッドを呼ぶ
end
end
# モジュール(Mixin)
module Swimmable
def swim = "#{name}が泳いでいる"
end
module Runnable
def run(speed: 'fast') = "#{name}が#{speed}で走っている"
end
class Duck < Animal
include Swimmable # インスタンスメソッドを追加
include Runnable
def speak = 'クワッ!'
end
duck = Duck.new('ドナルド')
duck.swim # 'ドナルドが泳いでいる'
duck.run # 'ドナルドがfastで走っている'
# extend: クラスメソッドとして追加
module ClassMethods
def create(attrs)
new(attrs[:name])
end
end
Duck.extend(ClassMethods)
# prepend: メソッドルックアップチェーンの前に挿入
module Logging
def speak
result = super
puts "[LOG] speak called, returned: #{result}"
result
end
end
Dog.prepend(Logging)
# 祖先チェーン
Duck.ancestors
# [Duck, Runnable, Swimmable, Animal, Object, Kernel, BasicObject]メタプログラミング
method_missing・define_method・open class・respond_to_missing?
# オープンクラス(既存クラスの拡張)
class Integer
def factorial
return 1 if self <= 1
self * (self - 1).factorial
end
def times_do
i = 0
while i < self
yield i
i += 1
end
end
end
5.factorial # 120
# Refinements(スコープを絞ったオープンクラス)
module StringExtensions
refine String do
def palindrome?
self == self.reverse
end
end
end
using StringExtensions # このファイル内でのみ有効
'racecar'.palindrome? # true
# define_method(動的メソッド定義)
class MyClass
%w[foo bar baz].each do |name|
define_method("say_#{name}") do
puts name
end
end
end
MyClass.new.say_foo # 'foo'
# method_missing(未定義メソッドの捕捉)
class DynamicFinder
def method_missing(name, *args)
if name.to_s.start_with?('find_by_')
attr = name.to_s.sub('find_by_', '')
"#{attr}=#{args.first}で検索"
else
super # 処理できない場合は super を呼ぶ
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?('find_by_') || super
end
end
finder = DynamicFinder.new
finder.find_by_name('Alice') # 'name=Aliceで検索'
finder.respond_to?(:find_by_id) # true