目的
テックキャンプのカリキュラムで「オブジェクト指向」について学んだので、その備忘録として残します。
概要
オブジェクト指向1とは、
アプリケーションを作成するときに、登場する役割ごとに分けて実装する方針
のことを言います。
RubyなどのWebアプリケーションに使用される言語の多くが、オブジェクト指向で実装する必要があります。
オブジェクト指向に沿うメリット
オブジェクト指向、つまりクラスに含まれる値や処理のまとまりを意識しながら、役割ごとにオブジェクトを分けて実装することで
- 実装がしやすくなる
- 後からコードを改編するときも、他のオブジェクトに影響しなくなる
といったメリットがあります。
オブジェクト指向に沿ったアプリケーションを考える
ここでは、以下のような
切符の券売機のアプリケーションの実装を例にして考えます。
用意すべきクラスを考える
まずは、このアプリケーションについてどんなクラスを用意すべきかを考えます。
クラスを考える際は、**「このアプリケーションにおいて、どのような処理が存在するのか」**に着目します。
券売機アプリケーションには、以下のような処理が存在しそうです。
- 乗車券のデータを作成する
- ユーザーに販売している商品を見せる
- 購入したい乗車券を決める
- お金を投入する
- 投入金額からお釣りの計算をする(購入処理を行う)
これらの処理を、役割の種類ごとに分類し、それぞれをクラスとします。上記を分類すると、以下のような形になります。
クラス | 意味 | 何をするのか |
---|---|---|
Ticket | 販売する乗車券 | 登録された金額を実体のあるデータ(インスタンス)にする |
TicketMachine | 券売機 | ユーザーに販売している乗車券を見せて、投入金額からお釣りの計算をする |
User | 購入する人 | 購入したい乗車券を決めて、お金を投入する |
このアプリケーションには、このように少なくとも3つのクラスが必要ということになります。
「券売機のアプリケーションなので、券売機クラスだけ用意すればいいのでは?」と考えた方もいるかもしれません。
しかしその場合、「券売機が行うべきでないこと」も、券売機のクラスに含まれてしまうことになります。
例えば、TicketMachine
クラスから見た場合、
- 乗車券のデータを作成する
- →券売機は、乗車券のデータそのものを作り出すことはできないのでは?
- 購入したい乗車券を決める、お金を投入する
- →これは券売機ではなく、購入者が行うことでは?
と言った疑問が生じます。
このようなアプリケーションになってしまうと、後からこのアプリケーションを修正する人は、
「なぜ券売機自身が投入金額を決めているの?」
と疑問を抱き、修正がしにくくなり、保守性が悪くなってしまいます。
それぞれのクラスには、明確な役割が1つだけ与えられている必要があります。これを単一責任の法則2と言います。
コードの記述
上記のクラスの情報をもとに、実際にコードを記述していきます。
(今回はコードの記述過程は省略します。)
今回は、クラス毎にファイルを分割して実装するため、
-
application.rb
(requireで他の3つのファイルを読み込む) user.rb
ticket.rb
-
ticket_machine.rb
の4つに分けてコードを記述します。
require "./ticket"
require "./user"
require "./ticket_machine"
puts "乗車券を用意してください"
tickets = []
3.times do |i|
puts "行き先を入力してください"
ticket_name = gets.chomp
puts "金額を入力してください"
ticket_fee = gets.to_i
tickets << Ticket.new(ticket_name,ticket_fee)
end
ticket_machine = TicketMachine.new(tickets)
ticket_machine.show_tickets
# 生成したインスタンスに対してshow_ticketsメソッドを適用
puts "投入金額を決めてください。"
money = gets.to_i
user = User.new(money)
ticket_machine.sell(user)
# 投入金額を決める
class User
def initialize(money)
@money = money
end
def money
@money
end
def choose_ticket
gets.to_i
end
end
class Ticket
def initialize(name,fee)
@name = name
@fee = fee
end
def name
@name
end
# インスタンス変数の値のみを戻り値としたメソッドのことをゲッターと呼ぶ。
def fee
@fee
end
end
class TicketMachine
def initialize(tickets)
@tickets = tickets
end
def tickets
@tickets
end
def show_tickets
puts "いらっしゃいませ。以下の商品を販売しています。"
i = 0
self.tickets.each do |ticket|
# rubyのインスタンスメソッド内でのself記述は特別。ここではselfが書かれているインスタンスメソッドを適用したインスタンス自身が代入されていると考える。
# 今回の場合、TicketMachineクラスのインスタンスが代入されている。
puts "【#{i}】#{ticket.name}: #{ticket.fee}円"
i += 1
end
end
# 選んだ商品をユーザーに対して販売する
def sell(user)
puts "商品を選んでください"
chosen_ticket = user.choose_ticket
change = user.money - self.tickets[chosen_ticket].fee
if change >= 0
puts "ご利用ありがとうございました。お釣りは#{change}円です。"
else
puts "投入金額が足りません。"
end
end
end
ここで、用意したクラスが券売機クラスのみだったと仮定すると、`ticket_machine.rb`に加え`ticket.rb`や`user.rb`の内容が混在してしまいます。 そうなると、他の開発者と共同で開発を行なっている場合に、どこを修正すればいいのか非常に分かりづらくなってしまいます。
終わりに
ここまで読んで頂き、ありがとうございました。
コメント欄で改善点等、記事に関するご指摘を頂けると更に励みになりますので、よろしくお願いします。