モデリング
モデリングについて
本格的なプログラムを作るにあたって「モデリング」あるいは「抽象化」が必須となる。これまでは、クラスとインスタンスの仕組みを利用してレビューアプリを作ることができた。その際に作ったクラスは「Review」クラス一つだったが、実際のプログラムでは複数のクラスが連携して動作する。やりたいことをプログラムに落とし込むために必要な工程の一つが、モデリングである。
クラスとインスタンスを使えば世の中に存在する様々な物事やシステムをRubyの世界で表現することができる。といっても、完全に表現しきれる訳ではない。
例えば犬を飼って楽しむゲームを作ろうとして「犬」を表現するためにDog
というクラスを作ったとしても、現実世界の犬が持つ情報は多すぎて、全てを表現し切ることは不可能に近い。
実際には、現実世界の犬が持つ情報からシステムに必要な要素のみを抽出して再現することになる。例えば、「犬には性別がある」「犬には犬種がある」「犬は鳴く」「犬は食べる」「犬は歳を取る」といった具合。
クラスとインスタンスの仕組みを使ってプログラムを作ろうとすると、データや動作について考える。先に挙げた例から考えれば、犬の「性別」「犬種」についてはデータである。そして、「犬は鳴く」「犬は食べる」「犬は歳を取る」といったものが動作である。オブジェクト指向では、この動作のことを振る舞い
と呼ぶ。そして、作りたいものに合わせて現実の犬から必要なデータや振る舞いを選択することを「モデリング」あるいは「抽象化」と呼ぶ。モデリングの肝は取捨選択だ。システムの仕様を考えた時、どんなデータや振る舞いが求められるかをよく考える必要がある。
例えば犬を飼って楽しむゲームに「犬は歳を取る」という機能は必要かは、そのゲームの方向性による。もしも「現実世界のペットは必ず死んでしまうので悲しい」という問題を解決するために作られたゲームなのであれば「歳を取る」という振る舞いは必要ないということにもなり得る。
言語を問わず重要な知識
以前作成した「Review」クラスもそうだが、プログラムの書き手が任意で「データ」と「振る舞い」を合わせてクラスを作れるというRubyの仕様は抽象データ型
という考えに基づいている。
他のオブジェクト指向の言語、例えばPHPやJava、JavaScriptにおいても同様の「クラスとインスタンス」の仕組みでプログラムを組んでいく。別の言語を扱うことになったとしても、原理原則は全く同じ。
モデリング実践
実際のコードの例、「映画を見るために発券機でチケットを購入する」というシチュエーションをモデリングしてみる。
チケット発券システムを開発
1.動作確認
まず、完成したプログラムがどのように動くかをみる。あくまで紹介なので、コードを書いたり手を動かす必要はない。
プログラムを起動する
プログラムを起動。exec.rbは、プログラム全体を動かすための起点となるコードなどが書かれたファイル。
1 |
$ ruby exec.rb
|
すると、以下のような選択肢が出てくる。
3つの映画の選択肢が出て、どれかを選択できる状態になる。
選択肢を選ぶ
例えばここで0と入力しエンターを押すと、以下のようになる。
どの映画のチケットを買ったのか、そして所持金がいくら残ったのかが表示されている。
2.要件定義
以上を踏まえて、今回どんなプログラムを作るのか整理すると以下のようになる。
1.実行すると、選択できる映画の一覧が表示され入力待ちになる
2.どれかを選択すると、どのチケットを買っていくら所持金が残ったのか表示される
3.プログラムに登場するものを考える
クラスとインスタンスを用いてコードを書いていくことを考える。まずは、プログラムに必要なものを考える。
誰かが発券機を利用することは間違いないので、「お客さん」と「発券機」は必要そうである。また、現在上映している映画があるはずなので「映画」と、そして発券される「チケット」も必要である。
ということで、「お客さん」「発券機」「映画」「チケット」を用意すべき。
4.それぞれの要素に対応するクラスを用意する
「お客さん」「発券機」「映画」「チケット」に相当するクラスを、それぞれ「Customer」「TicketVendingMachine」「Movie」「Ticket」として用意する。
以下の指示に従い、4つのクラスを用意する
まずは、デスクトップにフォルダを作成。
1 2 3 4 5 6 |
テキストエディタを開き「ruby_ticket_system」フォルダをOpen。Commandキー + o
で、該当フォルダを選択。
続いて「ruby_ticket_system」の中に「customer.rb」「ticket_vending_machine.rb」「movie.rb」「ticket.rb」を作成。
作成するファイルの中身はそれぞれ以下の通り。
1 2 |
class Customer
end
|
1 2 |
class TicketVendingMachine
end
|
1 2 |
class Movie
end
|
1 2 |
class Ticket
end
|
全て作成し終わったら、テキストエディタ上では以下のようになっているはず。
5.各クラスのインスタンスが持つ特徴を考えコードに反映させる
続いて、チケット発券システムにおいてそれぞれのクラスがどのようなデータや振る舞いを持つべきかを考える。
movieが持つべきデータ
movie、つまり映画自体は、以下のようなデータを持つことにする。
データ | コード上での名称 |
---|---|
タイトル | title |
金額 | fee |
上映開始日 | start_date |
上映終了日 | end_date |
続いて、Movieクラスにこの情報を反映させる。Movieクラスのインスタンスが初期化される時は、必ず上記のデータを持たせることにする。
1 2 3 4 5 6 7 8 |
class Movie
def initialize(title, fee, start_date, end_date)
@title = title
@fee = fee
@start_date = start_date
@end_date = end_date
end
end
|
これで、MovieクラスがあるファイルでMovie.new
とした際に引数で情報を渡し、その値をインスタンス変数に格納することができるようになる。
ticket_vending_machineが持つべきデータ
続いてチケット発券機を表すticket_vending_machine
データ | コード上での名称 |
---|---|
発券できる映画一覧 | movies |
発券機が持つべきデータは、現在発券可能な映画の情報である。発券可能な映画は一つであるとは限らないため、複数のデータをまとめて持てると良い。そこで、moviesというデータは配列を持つことにする。
1 2 3 4 5 |
class TicketVendingMachine
def initialize
@movies = []
end
end
|
後ほどこの配列にMovieクラスのインスタンスが格納されることになる。
ticketが持つべきデータ
続いてticketについて。
データ | コード上での名称 |
---|---|
タイトル | title |
金額 | fee |
期限 | limit |
実際に発券機でチケットを買うことを想像する。発券されてくるチケットのタイトルや料金は、観る映画によって変わるはずである。なので、選択された映画のタイトルや料金を、チケットにそのまま反映させる。そのため、Ticketクラスがinitialize
メソッドで初期化される際は必ずMovieクラスのインスタンスを受け取り、そのデータを利用することにする。limitはチケットが利用できる期限。発券された日のみ利用できる、という仕様にしておく。
Movieクラスのインスタンスはインスタンス変数@title
や@fee
を持つ。しかし、これらタイトルや金額といった情報をTicketクラスの内部から取得するにはどうすれば良いのか?Ticketクラスのinitialize
メソッドの中では、Movieクラスのインスタンスのインスタンス変数を直接参照することはできない。
ゲッター
ゲッターとは、あるクラスのインスタンスのインスタンス変数の値を返すだけのメソッド。これを定義することで、インスタンスから自身の持つインスタンス変数の値を取得することができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Movie
def initialize(title, fee, start_date, end_date)
@title = title
@fee = fee
@start_date = start_date
@end_date = end_date
end
def title
return @title
end
def fee
return @fee
end
end
|
ここで定義したのがゲッターである。こうすると、インスタンスは自身のインスタンス変数titleやインスタンス変数feeの値を返す。つまり、movieクラスのインスタンス.title
などとすることで、それぞれの値を取り出せるようになった。
また、後ほどticket_vending_machineのゲッターも必要となるので、今のうちに定義しておく。
1 2 3 4 5 6 7 8 9 |
class TicketVendingMachine
def initialize
@movies = []
end
def movies
@movies
end
end
|
続いてticket.rbについても編集する。
1 2 3 4 5 6 |
class Ticket
def initialize(movie)
@title = movie.title
@fee = movie.fee
end
end
|
ここで、movie.title
やmovie.fee
となっている部分に、ゲッターを定義したことで、movieクラスのインスタンス変数title
とfee
の値がそのままticketクラスのインスタンス変数に格納された。
customerが持つべきデータ
最後にお客さんを表すCustomerクラスについて。
データ | コード上での名称 |
---|---|
名前 | name |
所持金 | money |
チケット | ticket |
Customerクラスのインスタンスは、チケットを購入した結果最終的にTicketクラスのインスタンスを自らのインスタンス変数ticketに格納する。
1 2 3 4 5 |
class Customer
def initialize(name, money, ticket=nil)
@name, @money, @ticket = name, money, ticket
end
end
|
これまで通り初期化の際にインスタンス変数に値をセットしているだけだが、2つばかり新しい文法が登場している。
まずは、変数代入時に1行で複数の変数に値をセットするやり方。この場合、@nameにnameの値が、@moneyにmoneyの値が、@ticketにticketの値がそれぞれセットされる。名前ではなく、順番によって格納される値が決まっていることに注意。
もう一つは、initializeメソッドの引数にあるticket=nil
という書き方。これはデフォルト引数といって、もし該当する本引数が無い場合に代わりに=の右辺の値が代入される書き方。こうすることで、もしかしたら引数が送られてこないという想定のメソッドを用意することができるようになる。
6.プログラムを起動するためのファイル「exec.rb」を用意する
このプログラム全体を起動する準備をしたり、クラスが書かれたファイル達をまとめたりするためにもう一つexec.rb
というファイルを作成する。名前は何でも構わないが、execは「execute」の略で「実行する」という意味。
今回のプログラムの実行は、全てこのexec.rb内で行う。exec.rbを起動することで全てのプログラムが動作するようにする。
そのためには、exec.rbに全てのファイルの記述内容が存在することにする必要がある。
require
require
は、あるファイルから他のファイルの記述内容を参照するためのメソッド。
例えばhoge.rbとfuga.rbいうファイルがあった時、fuga.rbにrequire "./hoge"
と書くことでfuga.rbにhoge.rbの記述が書かれていることにしてくれる。
「なら始めから一つのファイルに全てのクラスをまとめて書けば良いじゃ無いか」と思うかもしれないが、それではクラスの中身が大きくなってきた時に不便である。実はプログラムはなるべく細かく分かれていた方が管理しやすい。
ruby_ticket_system
フォルダにexec.rbを新規作成し、以下のように編集する
require
メソッドの引数にはファイルの拡張子は必要ない。
1 2 3 4 |
require './customer'
require './movie'
require './ticket'
require './ticket_vending_machine'
|
これでexec.rbを実行した時に全てのファイルが読み込まれるようになった。
7. exec.rbを実行した時に必要な情報がセットされるようにする
今の所、exec.rbを実行しても何も起こらない。そこで、exec.rb内で、これまで作ってきたクラスからインスタンスを作る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
require './customer'
require './movie'
require './ticket'
require './ticket_vending_machine'
#customerを作成。nameとmoneyの値を渡している
customer = Customer.new("takashi", 5000)
#ticket_vending_machineを作成。初期値はinitializeメソッドの中で自動で入る
ticket_vending_machine = TicketVendingMachine.new
#movieを3つ作成。start_date、end_dateにはDateクラスのインスタンスを入れる
titanic = Movie.new(
"Titanic",
2000,
Date.new(2017, 2, 20),
Date.new(2017, 4, 20)
)
tom_and_jerry = Movie.new(
"tom_and_jerry",
1500,
Date.new(2017, 3, 18),
Date.new(2017, 5, 10)
)
oceans_eleven = Movie.new(
"Ocean's Eleven",
1800,
Date.new(2017, 4, 18),
Date.new(2017, 6, 10)
)
#作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
|
それぞれのクラスのインスタンスを作成している。いくつか注意すべきポイントがある。
newメソッドに渡している引数について
まず、作成時にnew
メソッドに対して必要な引数を渡していることに注目。これらはそれぞれのクラスのinitializeメソッドの引数として渡していた、そのクラスのインスタンスであれば必ず持っていなければならない値。
Dateクラスについて
Movieクラスのインスタンスに渡しているDate.new()
に注目。こちらはRubyにもともと備わっているクラスの一つで、日付を表現するためのクラス。Dateクラスを使って日付を扱えば、うるう年についてなど面倒な日付関連の計算が楽になる。Dateクラスを利用するには明示的にrequire
を使って読み込む必要があるためexec.rbを編集。
Dateクラスのインスタンスには、第一引数から順に年、月、日の順で数字で値を渡す。詳細は、以下のRubyの公式ドキュメントの例などを確認。
https://docs.ruby-lang.org/ja/latest/method/Date/s/new.html
1 2 3 4 5 6 7 |
require './customer'
require './movie'
require './ticket'
require './ticket_vending_machine'
require 'date'
#以下略
|
インスタンス変数の値の更新について
編集したexec.rbの下の方に、以下のような記述がある。
1 2 |
#作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
|
結論から言うと、このようにすることでticket_vending_machineのインスタンス変数@movies
の値が[titanic, tom_and_jerry, oceans_eleven]
に更新される。
真ん中に=
があるということは、「右の値を左の変数に代入する」という式に見える。しかし左辺はticket_vending_machine.movies
となっており、メソッドのような形である。これはどういうことか?
これは、先に出てきたゲッターと関連する文法である。TicketVendingMachineクラスに以下のようにメソッドを用意することでインスタンス変数の値の更新を実現している。
1 2 3 4 5 6 7 8 9 |
class TicketVendingMachine
def initialize
@movies = []
end
def movies=(movies)
@movies = movies
end
end
|
moviesという文字が出て来過ぎて混乱するが、要するにmovies=
という名前のメソッドが受け取った引数の値を@moviesに代入している、ということ。
セッター
インスタンス変数の値を更新するためのインスタンスメソッドのことを、特別にセッターと呼ぶ。
セッターを使用せずに値を更新する場合、下記の図のように更新ができない。
一見「=が一つの代入式」に見えたのは、メソッド名に=が含まれていたから。Rubyはメソッド名であっても半角スペースを無視するため、このようなことができる。
これで全てのインスタンスに必要な情報を渡し、初期化することができた。
8.仕様に沿った動作をする振る舞い(メソッド)を定義する
最初の仕様である「選択できる映画一覧が表示され入力待ちになる」という部分から実装していく。
選択できる映画一覧が表示され入力待ちになる
3つの映画の選択肢が出て、どれかを選択できる状態になる。メッセージを出すためにputs
メソッドを、ユーザーが選択肢を選べるようにgets
メソッドを使うと良さそう。
実装するメソッドの中身は以下のようになる。
1 2 3 4 5 6 7 |
puts "どの映画を見ますか?"
i = 0
self.movies.each do |movie|
puts "#{i} #{movie.title}: #{movie.fee}円"
i += 1
end
gets
|
途中で見慣れないself
というものが出て来た。
self
インスタンスメソッドの中でselfと書くと、そのインスタンスメソッドを使っているインスタンス自身を参照できる。
つまり、self.movies
の返り値は今回の場合[titanic, tom_and_jerry, oceans_eleven]
となる。その後、each
メソッドで一つ一つのmovieが参照され、それぞれのタイトルと金額が並んで表示される形になる。
これが実行されれば、ひとまずの仕様は実現できる。ここで問題になるのは、この実装はどのクラスが持つべきかということとどんな名前にするのかということ。まず前者について考える。
どのクラスが持つべきか?
メニューを表示するのは、明確に発券機の仕事である。なので、この処理はTicketVendingMachineクラスの振る舞いとして定義する。
続いて後者について考える。
どんな名前にすべきか?
この処理は映画の一覧を表示しユーザーに選択を促している。また、メニューの表示をしているとも言える。色々な捉え方ができるが、今回は「display_menu」という名前にすることにする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class TicketVendingMachine
def initialize
@movies = []
end
def movies
@movies
end
def movies=(movies)
@movies = movies
end
def display_menu
puts "どの映画を見ますか?"
i = 0
self.movies.each do |movie|
puts "#{i} #{movie.title}: #{movie.fee}円"
i += 1
end
gets
end
end
|
メソッドを定義できたので、exec.rbにdisplay_menu
を呼び出す記述を追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
require './customer'
require './movie'
require './ticket'
require './ticket_vending_machine'
require 'date'
#customerを作成。nameとmoneyの値を渡している
customer = Customer.new("takashi", 5000)
#ticket_vending_machineを作成。初期値はinitializeメソッドの中で自動で入る
ticket_vending_machine = TicketVendingMachine.new
#movieを3つ作成。start_date、end_dateにはDateクラスのインスタンスを入れる
titanic = Movie.new(
"Titanic",
2000,
Date.new(2017, 2, 20),
Date.new(2017, 4, 20)
)
tom_and_jerry = Movie.new(
"tom_and_jerry",
1500,
Date.new(2017, 3, 18),
Date.new(2017, 5, 10)
)
oceans_eleven = Movie.new(
"Ocean's Eleven",
1800,
Date.new(2017, 4, 18),
Date.new(2017, 6, 10)
)
#作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
ticket_vending_machine.display_menu
|
ここまで書けたら、一度exec.rbを実行。
1 2 3 4 5 6 7 |
#exec.rbを実行
$ ruby exec.rb
#以下のように表示されれば成功
どの映画を見ますか?
0 Titanic: 2000円
1 tom_and_jerry: 1500円
2 Ocean's Eleven: 1800円
|
うまく表示されなかった場合、エラーメッセージを読んで解決を試みる。
どれかを選択すると、どのチケットを買っていくら所持金が残ったのか表示される
これもまた、処理を「どのクラスが持つべきか」「どんな名前のメソッドにするか」を考える。
どのクラスが持つべきか
どれかを選択するのは、明確にcustomerの役割なので、映画を選択する、という処理はCustomerクラスに定義すべきである。また、どのチケットを買っていくら所持金が残ったのか表示するのは、ticket_vending_machineの役割に思われるので、こちらの処理はTicketVendingMachineクラスに定義する。
どんな名前のメソッドにするか
「映画を選択する」処理なので、ここは素直に「choose_movie」とするのが良い。「どのチケットを買いいくら所持金が残ったのかを表示する」のは「display_result」とする。
ゲッター、セッターを楽に定義する
今回はCustomerクラスにゲッターを実装する。しかし今回はインスタンス変数の数が多く、全てのゲッターを地道に定義すると行数が多くなってしまう。そんな悩みを解決するために、attr_accessor
というメソッドがある。
attr_accessor
ゲッター、セッターを簡単に定義できるメソッド。以下のように利用する。
例
1 2 3 |
class Dog
attr_accessor :name, :type, :age
end
|
これは、以下の定義と同様に評価される。
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Dog
def name
@name
end
def name=(name)
@name = name
end
def type
@type
end
def type=(type)
@type = type
end
def age
@age
end
def age=(age)
@age = age
end
end
|
これだけの行数が削減できるのは非常に便利。積極的に使っていく。
名前と処理を置くクラスが決まったので、早速メソッドを定義する。
1 2 3 4 5 6 7 8 9 10 11 |
class Customer
attr_accessor :name, :money, :ticket
def initialize(name, money, ticket=nil)
@name, @money, @ticket = name, money, ticket
end
def choose_movie
gets.to_i
end
end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class TicketVendingMachine
def initialize
@movies = []
end
def movies
@movies
end
def movies=(movies)
@movies = movies
end
def display_menu
puts "どの映画を見ますか?"
i = 0
self.movies.each do |movie|
puts "#{i} #{movie.title}: #{movie.fee}円"
i += 1
end
gets
end
def display_result(customer)
choosed_movie = self.movies[customer.choose_movie]
customer.money -= choosed_movie.fee
puts "#{choosed_movie.title}のチケットを買ったよ!"
puts "#{customer.name}の所持金が#{customer.money}円になりました!"
end
end
|
これでcustomerが選択した映画の番号によって配列からmovieクラスのインスタンスが取り出され、choosed_movieという変数に代入されるようになる。この時点でdisplay_menu
メソッドの中のgets
メソッドは必要がなくなったので削除する。
1 2 3 4 5 6 7 8 9 |
def display_menu
puts "どの映画を見ますか?"
i = 0
self.movies.each do |movie|
puts "#{i} #{movie.title}: #{movie.fee}円"
i += 1
end
end
end
|
続いて、exec.rbにもdisplay_resultメソッドが実行されるよう編集を加える。
1 2 3 4 5 |
# 作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
ticket_vending_machine.display_menu
ticket_vending_machine.display_result(customer)
|
これで、exec.rbを実行。恐らく、仕様通りに動くはず。
1 2 3 |
#exec.rbを実行
$ ruby exec.rb
#仕様通り実行されれば成功
|
9.リファクタリング
コードの仕様を変えずに中身を改善することをリファクタリングと言う。
とりあえず仕様通りに動くものはできたが、コードの内容を改善できる部分がある。例えば、読みやすさ(可読性)である。
現状exec.rbの最後の実行部分のコードはdisplay_resultメソッドを利用しているが、display_menu
が呼ばれてすぐ次にdisplay_result
が呼ばれているので、何に対しての結果なのかが一見してわかりづらくなっている。
これを改善するには、ユーザーがチケットを買う手順について再考した方が良さそうである。
そもそもチケットを買うという行為は誰が主導するのか。今回の場合は、customerの行為になる思われる。つまり、exec.rbの実行部分は以下のように始まるのが正しいと考えられる。
1 2 3 4 |
#作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
customer.buy(ticket_vending_machine)
|
引数としてticket_vending_machineを渡しているのは、お客さんがチケットを買う券売機を選んで取引を始めるイメージである。
では、customer.buy
としてコードを書き換える。
1 2 3 4 5 |
#省略
#作成したticket_vending_machineクラスのインスタンスのmoviesを更新
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
customer.buy(ticket_vending_machine)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Customer
attr_accessor :name, :money, :ticket
def initialize(name, money, ticket=nil)
@name, @money, @ticket = name, money, ticket
end
def choose_movie
gets.to_i
end
def buy(ticket_vending_machine)
ticket_vending_machine.display_menu
choosed_movie = ticket_vending_machine.movies[choose_movie]
self.money -= choosed_movie.fee
self.ticket = Ticket.new(choosed_movie)
puts "#{choosed_movie.title}のチケットを買ったよ!"
puts "#{name}の所持金が#{money}円になりました!"
end
end
|
このように、同じ仕様を実現するにあたって書けるコードのパターンは無限大である。
加えて、オブジェクト指向という観点においては明確な優劣が存在する。どんなコードが優秀で、どんなコードがダメなのか、十分に考察する。
完成コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
require './customer'
require './movie'
require './ticket'
require './ticket_vending_machine'
require 'date'
customer = Customer.new("takashi", 5000)
ticket_vending_machine = TicketVendingMachine.new
titanic = Movie.new(
"Titanic",
2000,
Date.new(2017, 2, 20),
Date.new(2017, 4, 20)
)
tom_and_jerry = Movie.new(
"tom_and_jerry",
1500,
Date.new(2017, 3, 18),
Date.new(2017, 5, 10)
)
oceans_eleven = Movie.new(
"Ocean's Eleven",
1800,
Date.new(2017, 4, 18),
Date.new(2017, 6, 10)
)
ticket_vending_machine.movies = [titanic, tom_and_jerry, oceans_eleven]
customer.buy(ticket_vending_machine)
|
1 2 3 4 5 6 |
class Ticket
def initialize(movie)
@title = movie.title
@fee = movie.fee
end
end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Customer
attr_accessor :name, :money, :ticket
def initialize(name, money, ticket=nil)
@name, @money, @ticket = name, money, ticket
end
def choose_movie
gets.to_i
end
def buy(ticket_vending_machine)
ticket_vending_machine.display_menu
choosed_movie = ticket_vending_machine.movies[choose_movie]
self.money -= choosed_movie.fee
self.ticket = Ticket.new(choosed_movie)
puts "#{choosed_movie.title}のチケットを買ったよ!"
puts "#{name}の所持金が#{money}円になりました!"
end
end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class TicketVendingMachine
def initialize
@movies = []
end
def movies
@movies
end
def movies=(movies)
@movies = movies
end
def display_menu
puts "どの映画を見ますか?"
i = 0
self.movies.each do |movie|
puts "#{i} #{movie.title}: #{movie.fee}円"
i += 1
end
end
def display_result(customer)
choosed_movie = self.movies[customer.choose_movie]
customer.money -= choosed_movie.fee
puts "#{choosed_movie.title}のチケットを買ったよ!"
puts "#{customer.name}の所持金が#{customer.money}円になりました!"
end
end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Movie
def initialize(title, fee, start_date, end_date)
@title = title
@fee = fee
@start_date = start_date
@end_date = end_date
end
def title
return @title
end
def fee
return @fee
end
end
|