5月10日

ジュースを3種類管理できるようにする。
在庫にレッドブル(値段:200円、名前”レッドブル”)5本を追加する。
在庫に水(値段:100円、名前”水”)5本を追加する。
投入金額、在庫の点で購入可能なドリンクのリストを取得できる。

3種類のドリンクに対応するために、Stockを3個の要素を持つ配列にしてみる。
コンパイルエラーが発生するので、stock変数にアクセスしている部分をすべてstock[0]に変更する。
そうすると、コンパイルエラーは消せる。

package tdd;

public class VendingMachine {
	private int amount = 0;
	private int saleAmount = 0;
	private Stock[] stock;

	public VendingMachine() {
		stock = new Stock[3];
		stock[0] = new Stock();
		stock[0].setCount(5);
		stock[0].setName("コーラ");
		stock[0].setPrice(120);
	}

	public int insert(int money) {
		if (!isAcceptable(money)) return money;
		amount += money;
		return 0;
	}

	private boolean isAcceptable(int money) {
		if (money == 10) return true;
		if (money == 50) return true;
		if (money == 100) return true;
		if (money == 500) return true;
		if (money == 1000) return true;
		return false;
	}

	public int getAmount() {
		return amount;
	}

	public int refund() {
		int a = amount;
		amount = 0;
		return a;
	}

	public Stock getStock() {
		return stock[0];
	}

	public boolean isPurchasable() {
		if (amount < 120) return false;
		if (stock[0].getCount() == 0) return false;
		return true;
	}

	public Coke purchase() {
		if (!isPurchasable()) return null;
		amount -= 120;
		saleAmount += 120;
		int c = stock[0].getCount();
		stock[0].setCount(c - 1);
		return new Coke();
	}

	public int getSaleAmount() {
		return saleAmount;
	}

	public String[] getPurchasableNames() {
		return new String[1];
	}
}

追加したテストがパスするように実装する。

	public String[] getPurchasableNames() {
		if (amount >= 100) {
			String[] s = new String[1];
			s[0] = "水";
			return s;
		}
		return new String[0];
	}

水を購入するテストを追加する。

	@Test
	public void test水を購入する() {
		VendingMachine vm = new VendingMachine();
		Water w = (Water) vm.purchase("水");
		assertNull(w);
		vm.insert(100);
		w = (Water) vm.purchase("水");
		assertNotNull(w);
	}

現状ではpurchase()メソッド内で引数をチェックしてないので、コーラの販売しかできない。
水を購入できるように、実装しなおす必要がある。

抽象クラス Drink を追加して、CokeとWaterがDrinkを継承する。

package tdd;

public abstract class Drink {

}
package tdd;

public class Water extends Drink {

}
package tdd;

public class Coke extends Drink {

}

飲み物を購入するメソッド purchase()に引数を追加して種類を指定できるようにする。
戻り値の型を Drink に変更して、CokeとWaterの両方を購入できるようにする。
水を購入できるかどうかを調べるisPurchasable()メソッドにも引数を追加して、種類を指定できるようにする。

	public boolean isPurchasable(String name) {
		if (name.equals("コーラ")) {
			if (amount < 120) return false;
			if (stock[0].getCount() == 0) return false;
			return true;
		}
		if (name.equals("水")) {
			if (amount < 100) return false;
			if (stock[2].getCount() == 0) return false;
			return true;
		}
		return false;
	}

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		amount -= 120;
		saleAmount += 120;
		int c = stock[0].getCount();
		stock[0].setCount(c - 1);
		return new Coke();
	}

purchase()メソッドを修正して、テストにパスするように実装する。

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		if (name.equals("コーラ")) {
			amount -= 120;
			saleAmount += 120;
			int c = stock[0].getCount();
			stock[0].setCount(c - 1);
			return new Coke();
		}
		if (name.equals("水")) {
			amount -= 100;
			saleAmount += 100;
			int c = stock[2].getCount();
			stock[2].setCount(c - 1);
			return new Water();
		}
		return null;
	}

getStock()メソッドも現状はコーラしか参照していないので、複数の飲み物に対応できるように修正する。
まずはテストを変更する。
getStock()メソッドに引数を追加して飲み物の種類を指定できるようにする。

	@Test
	public void testコーラの在庫() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock("コーラ");
		assertEquals(5, s.getCount());
		assertEquals("コーラ", s.getName());
		assertEquals(120, s.getPrice());
	}

	@Test
	public void test水の在庫() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock("水");
		assertEquals(5, s.getCount());
		assertEquals("水", s.getName());
		assertEquals(100, s.getPrice());
	}

テストにパスするように実装しなおす。

	public Stock getStock(String name) {
		for (Stock s : stock) {
			if (s.getName().equals(name)) return s;
		}
		return null;
	}

getStock()を実装しなおしたことにより、isPurchasable()とpurchase()の実装を簡素化できるので、両方のメソッドをリファクタリングする。

リファクタリング (refactoring) とは、コンピュータプログラミングにおいて、プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである。また、いくつかのリファクタリング手法の総称としても使われる。
リファクタリング (プログラミング) – Wikipedia

isPurchasable()メソッドをリファクタリングする。

	public boolean isPurchasable(String name) {
		Stock s = getStock(name);
		if (s == null) return false;
		if (amount < s.getPrice()) return false;
		if (s.getCount() == 0) return false;
		return true;
	}

同様に、purchase()メソッドもリファクタリングできる。
ただし、飲み物の種類によって戻り値として返すDrinkのインスタンスが異なるため、その違いをStockクラスで吸収する。

	public Drink getDrink() {
		if (name.equals("コーラ")) return new Coke();
		if (name.equals("水")) return new Water();
		return null;
	}

StockにgetDrink()を追加したことで、purchase()メソッドをリファクタリングして簡略化できる。

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		Stock s = getStock(name);
		amount -= s.getPrice();
		saleAmount += s.getPrice();
		int c = s.getCount();
		s.setCount(c - 1);
		return s.getDrink();
	}

うまく動いていない部分があるので、さらにテストを追加する。

	@Test
	public void test購入可能なドリンクのリストを取得() {
		VendingMachine vm = new VendingMachine();
		String[] names = vm.getPurchasableNames();
		assertEquals(0, names.length);
		vm.insert(100);
		names = vm.getPurchasableNames();
		assertEquals(1, names.length);
		assertEquals("水", names[0]);

		vm.insert(50);
		names = vm.getPurchasableNames();
		assertEquals(2, names.length);
	}

getPurchasableNames()を変更してテストにパスさせる。
コレクションフレームワークを使用して実装。

	public String[] getPurchasableNames() {
		ArrayList<String> list = new ArrayList<String>();
		for (Stock s : stock) {
			if (amount >= s.getPrice()) {
				list.add(s.getName());
			}
		}
		return list.toArray(new String[0]);
	}

レッドブルを購入するテストを追加する。

	@Test
	public void testレッドブルを購入する() {
		VendingMachine vm = new VendingMachine();
		RedBull r = (RedBull) vm.purchase("レッドブル");
		assertNull(r);
		vm.insert(200);
		r = (RedBull) vm.purchase("レッドブル");
		assertNotNull(r);
	}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です