step2 プリミティブ型と文字列型をラップする

この自動販売機に投入する金額を100や500などの整数値で渡していますが、現実の自動販売機では100円や500円などの硬貨を投入します。硬貨を表す定義をenumで定義してbuyメソッドに硬貨以外を渡せないようにしましょう。このように型を明示することによって、間違った値を指定できなくして不具合の要因を取り除くことができます。例えば、VendingMachine#buy(payment, kind_of_drink)の第一引数に510などの想定していない値を指定できなくなります。

下記コードはこちらの内容です。

class Coin
  ONE_HUNDRED = 100
  FIVE_HUNDRED = 500
end

硬貨と同様に、飲み物の種別も定数で定義しましょう。

class DrinkType
  COKE = 1
  DIET_COKE = 2
  TEA = 3
end

追加した定数をテストコードで使用するようにします。

def test500円でコーラを購入
-   drink = @vm.buy(500, Drink::COKE)
+   drink = @vm.buy(Coin::FIVE_HUNDRED, DrinkType::COKE)
    change = @vm.refund

-   assert_equal(Drink::COKE, drink.kind)
-   assert_equal(400, change)
+   assert_equal(DrinkType::COKE, drink.kind)
+   assert_equal([Coin::ONE_HUNDRED] * 4, change)
end

自動販売機内の商品の在庫数を整数値で保持しています。

class VendingMachine

  def initialize@quantity_of_coke = 5 # コーラの在庫数
    @quantity_of_diet_coke = 5 # ダイエットコーラの在庫数
    @quantity_of_tea = 5 # お茶の在庫数

この在庫数をラップするStockクラスを用意して、VendingMachine内で使用するようにしましょう。

class Stock

  def initialize(quantity)
    @quantity = quantity
  end

  def quantity@quantity
  end

  def decrement@quantity -= 1
  end

end

VendingMachineクラスでは、在庫の初期化、在庫数の確認、在庫の更新で、以下のように修正します。

class VendingMachine

   def initialize# 在庫数の初期化
-    @quantity_of_coke = 5 # コーラの在庫数
-    @quantity_of_diet_coke = 5 # ダイエットコーラの在庫数
-    @quantity_of_tea = 5 # お茶の在庫数
+    @stock_of_coke = Stock.new(5) # コーラの在庫数
+    @stock_of_diet_coke = Stock.new(5) # ダイエットコーラの在庫数
+    @stock_of_tea = Stock.new(5) # お茶の在庫数

     (...省略...)

   end

   def buy(payment, kind_of_drink)

     (...省略...)

     # 在庫数の確認
-    if kind_of_drink == Drink::COKE && @quantity_of_coke == 0
-      @change += payment
+    if kind_of_drink == DrinkType::COKE && @stock_of_coke.quantity == 0
+      @change.push(payment)
       return nil

     (...省略...)

     # 在庫数の更新
-    if kind_of_drink == Drink::COKE
-      @quantity_of_coke -= 1
-    elsif kind_of_drink == Drink::DIET_COKE then
-      @quantity_of_diet_coke -= 1
+    if kind_of_drink == DrinkType::COKE
+      @stock_of_coke.decrement
+    elsif kind_of_drink == DrinkType::DIET_COKE then
+      @stock_of_diet_coke.decrement
     else
-      @quantity_of_tea -= 1
+      @stock_of_tea.decrement
     end

意味のある値に対して型を定義することで、コードの可読性を高め、変更に強くし、拡張しやすくなります。一般的にValueObjectと呼ばれるテクニックで以下の特徴を持ちます。