オブジェクト指向について理解が浅かったので、記事にすることで少しでも理解を深めようという目的で書きました。
オブジェクト指向とは?
オブジェクト指向とは、「変更に対して柔軟に対応するための開発手法」です。
各オブジェクトに責務を任せることでコードが理解しやすく、変更や機能追加もしやすくなります。
オブジェクト指向を理解するために重要な要素が3つあります。「継承」「ポリモーフィズム」「カプセル化」です。
それぞれについて解説していきます。
継承
継承とは、共通の振る舞いを抽象化してまとめることです。
継承することで、親クラスの変数やメソッドが子クラスへ引き継がれます。
これによって、複数のクラス間で同じ機能を再利用しやすくし、コードの重複を減らすことでメンテナンスも簡単にすることができます。
class Product
def initialize(name, price)
@name = name
@price = price
end
def show_info
"商品名: #{@name}, 価格: #{@price}円"
end
end
class Book < Product
def initialize(name, price, author)
super(name, price)
@author = author
end
def show_info
"#{super} 著者: #{@author}"
end
end
product = Product.new("商品", 1000)
puts product.show_info
#=> 商品名: 商品, 価格: 1000円
book = Book.new("Ruby入門", 2800, "山田太郎")
puts book.show_info
#=> 商品名: Ruby入門, 価格: 2800円 著者: 山田太郎
上記のコードでは、「商品」という親クラスを作り、その子クラスとして「本」を定義しています。共通する特徴(商品名、価格)は親クラスに定義し、本特有の情報(著者)は子クラスに定義しています。
def show_info
"#{super} 著者: #{@author}"
end
子クラスでは、親クラスと同名のメソッドを定義することで、親クラスの処理を上書きすることができます。
Book
クラスのshow_info
メソッドでは、super
を使って親クラスのメソッドを呼び出した上で著者情報も含めた文字列を返しています。
これはProduct
を親クラスとして継承していることで実現しています。
ポリモーフィズム
呼び出すメソッドが同じでも、インスタンスによって違う振る舞いをすることをポリモーフィズムと言います。
class Product
def initialize(name, price)
@name = name
@price = price
end
def show_info
"商品名: #{@name}, 価格: #{@price}円"
end
end
class Book < Product
def initialize(name, price, author)
super(name, price)
@author = author
end
def show_info
"#{super} 著者: #{@author}"
end
end
class DVD < Product
def initialize(name, price, director)
super(name, price)
@director = director
end
def show_info
"#{super} 監督: #{@director}"
end
end
# 異なる種類の商品を作成
book = Book.new("Ruby入門", 2000, "田中 太郎")
dvd = DVD.new("映画A", 3000, "佐藤 監督")
# 異なる子クラスのインスタンスを同じ配列に格納
products = [book, dvd]
# 同じメソッド(show_info)を呼び出すことで、それぞれのインスタンスに応じた振る舞いが実行される
products.each do |product|
puts product.show_info
end
# =>商品名: Ruby入門, 価格: 2000円 著者: 田中 太郎
# =>商品名: 映画A, 価格: 3000円 監督: 佐藤 監督
product
は具体的なクラス(BookなのかDVDなのか)を意識せずに、同じメソッド(show_info
)で異なるクラスのオブジェクトを扱えています。
ポリモーフィズムを活用すると、コードがきれいにまとまり、変更にも強くなります。
カプセル化
オブジェクトのデータや機能を隠蔽し、外部から直接アクセスができないようにすることをカプセル化と言います。これにより、データの不正な変更を防ぎ、オブジェクトの内部状態を安全に保つことができます。
class Product
# インスタンス変数を読み取り専用にする
attr_reader :name, :price
def initialize(name, price)
@name = name
@price = price
end
def show_info
"商品名: #{@name}, 価格: #{@price}円"
end
end
class Book < Product
# インスタンス変数を読み取り専用にする
attr_reader :author
def initialize(name, price, author)
super(name, price)
@author = author
end
def show_info
"#{super} 著者: #{@author}"
end
end
product1 = Product.new("ノート", 300)
book1 = Book.new("Ruby入門", 2000, "田中 太郎")
puts product1.show_info # => 商品名: ノート, 価格: 300円
puts book1.show_info # => 商品名: Ruby入門, 価格: 2000円 著者: 田中 太郎
# エラー例: インスタンス変数への直接代入
product1.name = "新しいノート" #=> undefined method `name=' for an instance of Product (NoMethodError)
book1.price = 1800 #=> undefined method `price=' for an instance of Book (NoMethodError)
book1.author = "新しい著者" #=> undefined method `author=' for an instance of Book (NoMethodError)
attr_reader
を使ってインスタンス変数を読み取り専用にしているため、直接変更しようとするとエラーになります。
値を変更したい場合は、以下のように専用のメソッドを定義して行います。
class Book < Product
attr_reader :author
def initialize(name, price, author)
super(name, price)
@author = author
end
def show_info
"#{super} 著者: #{@author}"
end
# Bookクラスに著者を変更するメソッドを追加
def update_author(new_author)
@author = new_author
end
end
book1.update_author("新しい著者")
# 変更後のデータ表示
puts book1.show_info # => 商品名: Ruby入門, 価格: 2000円 著者: 新しい著者
今回理解を深めるために記事を書きましたが、オブジェクト指向についてまだまだ理解が足りないと感じています。
これからもより理解を深めていけるように頑張ります!
参考