Rubyで自動販売機のプログラムを作成する

## はじめに
[前回](https://qiita.com/at-sacai/private/eb933e1c8f73f28e6741)に引き続き、Ruby学習のアウトプットとして今回は自動販売機のプログラムを作成しました。

上記の内容について、備忘のためにメモ書きを残しておくことにします。

## 仕様

Suicaのみで購入可能な自動販売機のプログラムとします。

### Suica

- デポジットとして500円がデフォルトでチャージされているものとする
- Suicaには100円以上の任意の金額をチャージできる
- 100円未満をチャージしようとした場合は例外を発生させる
- Suicaは現在のチャージ残高を取得できる

### 自動販売機の在庫管理

- 自動販売機は飲み物を3種類（ペプシ、モンスター、いろはす）を格納できる
- 飲み物は名前と値段の情報をもつ
- 初期状態で、3種類の飲み物はそれぞれ5本格納されている
- 自動販売機は在庫を取得できる

|名前 |価格 |
|:------|:---|
| ペプシ   |150円|
| モンスター|230円|
| いろはす  |120円|

### 自動販売機の購入処理

- 自動販売機は3種類の飲み物が購入できるかどうかを取得できる
- 飲み物の値段以上のチャージ残高がある条件下で、購入操作を行うと以下の動作をする
    - 自動販売機は、該当の飲み物の在庫を減らす
    - 売り上げ金額を増やす
    - Suicaのチャージ残高を減らす
    - チャージ残高が足りない、もしくは該当の飲み物の在庫がない状態で購入操作を行った場合は例外を発生させる
- 自動販売機は、現在の売上金額を取得できる

### 制約

- Classを用いてオブジェクト指向で書く
    - 3つのクラスを定義する
- クラスのインスタンスは以下の通りとする
    - Suica：1枚で1インスタンス
    - 飲み物：1本で1インスタンス
- クラスのインスタンスメソッド内で標準入力を受け取るコードは避ける
    - 値を受け取りたい場合は、メソッドの引数などで渡す
- クラスのインスタンスメソッド内で標準出力するコードは避ける
    - 標準出力に出す必要はなく、値を返すメソッドを実装する
- `attr_accessor`の利用は避ける
    - セッターメソッド、ゲッターメソッドを定義する

## プログラムの検討

コーディングに入る前に、仕様が文字ばかりで理解がしづらいためシーケンス図に図解して整理しておくことにします。少し分かりやすくなった気がします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2290540/e45fde09-efb1-3867-f08e-3089a3fb7e0d.png)



それでは、コーディングしていきます。まずは、Suicaのクラスを作成します。

``` ruby:suica.rb
class Suica
  attr_reader :deposit

  def initialize
    @deposit = 500
  end

  def charge(money)
    if money < 100
      puts '最低チャージ可能金額は、100円からになります。'
    else
      @deposit += money
    end
  end

  def pay(money)
    @deposit -= money
  end
end

```

次に、飲み物のクラスです。

``` ruby:suica.rb
class Drink
  attr_reader :name, :price

  def initialize(name, price)
    @name = name
    @price = price
  end
end


```

最後に、自動販売機のクラスです。個人的には、在庫管理と購入処理の部分の難易度が他と比較し高く感じました。

学びとしては、購入処理（`buy`メソッド）内に`return`が必要ということです。なぜなら、`return`がないと以下のエラーが起きてしまいます。

※`return`は、`return`時点で処理を終了させることができます。

- `selected_drink`が`nil`の場合（飲み物の在庫がない場合）
    - `NilClass`には`price`メソッドが定義されていないため、`selected_drink.price`で`NoMethodError`が発生する
- チャージ残高が足りない場合
    - `return`がないため、チャージ残高が足りない場合でも後続の購入処理が走ってしまう

``` ruby: vending_machine.rb
class VendingMachine
  attr_reader :sales

  def initialize
    @sales = 0
    @stock = []
    5.times do
      @stock << Drink.new('pepsi', 150)
      @stock << Drink.new('monster', 230)
      @stock << Drink.new('irohasu', 120)
    end
  end

  def show_stock
    puts "pepsiの在庫は、#{@stock.count { |stock| stock.name == 'pepsi' }}個です"
    puts "monsterの在庫は、#{@stock.count { |stock| stock.name == 'monster' }}個です"
    puts "irohasuの在庫は、#{@stock.count { |stock| stock.name == 'irohasu' }}個です"
  end

  def add_stock(drink)
    @stock << drink
  end

  def buy(suica, drink)
    selected_drink = @stock.find { |stock| stock.name == drink }
    unless selected_drink
      puts "#{drink}の在庫がありません"
      return
    end

    if suica.deposit < selected_drink.price
      puts 'チャージ残高が足りません'
      return
    end

    @sales += selected_drink.price
    @stock.delete_at(@stock.find_index { |stock| stock.name == drink })
    suica.pay(selected_drink.price)
  end
end

```

## おわりに

今回、初めてオブジェクト指向プログラミングを体験しました。

自動販売機のお題は、複数のオブジェクト指向の要素が盛り込まれており難易度も適切だったかもしれません。やはり、期待する結果が出力された瞬間が楽しいです。

今後も引き続きプログラミングの学習を継続していきます。