自動販売機(Level2)では、小さいメソッドに処理を切り出すことを学びました。
しかし、まだMainクラス1つの中で完結しているプログラムです。
オブジェクト指向言語であるJavaを使うなら、クラス分けしたいところです。
Level3ではクラス分けして作成しました。
作成したのは、以下の4つです。
Items(商品)
Deposit(入金額)
VendingMachine(自動販売機)
Main(メインロジック)
どうやって書いたか?
どういうクラスが必要かを判断
・商品はクラスとして必要とすぐ判断できた。
・値オブジェクトとして、Depositクラスは必要と判断。
・自動販売機クラスは必要で、フィールドはItemsとDepositになる。
段階的にリファクタリング
・クラスを作成し、メソッドを移動
・Main側の処理をクラスに置き換え
・移動させたメソッドを使って動くかテスト
これの繰り返し。
Items(商品)
商品に関わる変数、処理(メソッド化できる箇所)をまとめます。
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
package vm; import java.util.HashMap; import java.util.Map; public class Items { private Map<String, Integer> items; // 商品リスト private Map<String, Integer> availablePurchases; // 購入可能商品 // コンストラクタ(商品リストの初期化) Items() { this.items = new HashMap<String, Integer>(); items.put("コーラ", 100); items.put("オレンジジュース", 120); items.put("水", 80); } // 最低購入金額の取得 int minPrice() { int minPrice = 0; int loopCount = 0; for (String itemKey : items.keySet()) { minPrice = validMinPrice(loopCount, minPrice, itemPrice(itemKey)); loopCount++; } return minPrice; } // 最低購入金額の判定 int validMinPrice(int loopCount, int minPrice, int price) { if (loopCount == 0 || minPrice > price) { minPrice = price; } return minPrice; } // 商品金額 private int itemPrice(String itemKey) { return items.get(itemKey); } // 購入可能商品金額 private int availablePurchasePrice(String itemKey) { return availablePurchases.get(itemKey); } // 購入可能商品リストの取得 void createAvailablePurchases(int deposit) { availablePurchases = new HashMap<String, Integer>(); for (String itemKey : items.keySet()) { addAvailablePurchase(deposit, itemKey); } } // 購入可能商品リストの追加 private void addAvailablePurchase(int deposit, String itemKey) { if (deposit >= items.get(itemKey)) { availablePurchases.put(itemKey, items.get(itemKey)); } } // 購入可能商品の表示 void showAvailablePurchase() { for (String itemKey : availablePurchases.keySet()) { System.out.println(itemKey + ":" + items.get(itemKey) + "円"); } } // 購入可能商品であるかの判定 boolean isAvailablePurchase(String itemName, Deposit deposit) { return availablePurchases.containsKey(itemName); } // 販売価格 int saleAmount(String itemName) { return availablePurchasePrice(itemName); } } |
Deposit(入金額)
入金されたお金に関する変数、処理をまとめます。
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 |
package vm; public class Deposit { private int deposit; // 入金額 // コンストラクタ(初期値0円) Deposit() { deposit = 0; } // 入金 void deposit(int amount) { deposit = deposit + amount; } // 現在入金額 int amount() { return this.deposit; } // 課金 void charge(int amount) { int charge = this.deposit - amount; this.deposit = charge; } } |
VendingMachine(自動販売機)
ItemクラスとDepositクラスを変数に持たせ、処理をまとめます。
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 39 40 41 42 43 44 45 46 47 48 |
package vm; public class VendingMachine { private Items items; // 商品 private Deposit deposit; // 入金額 // コンストラクタ VendingMachine() { items = new Items(); deposit = new Deposit(); } // 入金 void deposit(int amount) { deposit.deposit(amount); } // 最低価格商品以上の入金があるか判定 boolean hasMinDeposit() { if (deposit.amount() < items.minPrice()) { return false; } return true; } // 購入可能商品を表示 void showAvailablePurchases() { items.createAvailablePurchases(deposit.amount()); items.showAvailablePurchase(); } // 購入可能商品であるか判定 boolean isAvailablePurchases(String itemName) { return items.isAvailablePurchase(itemName, deposit); } // 購入商品を表示 void selectItem(String itemName) { System.out.println(itemName + "です!"); } // 課金 void charge(String itemName) { int saleAmount = items.saleAmount(itemName); deposit.charge(saleAmount); System.out.println("おつりは、" + deposit.amount() + "円です。"); } } |
Main(メインロジック)
各クラスの機能でメインロジックを組み上げます。
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 39 40 41 42 43 44 |
package vm; import java.util.Scanner; /** * 自動販売機(Level6) 自動販売機クラスの作成 */ public class Main { // メインロジック public static void main(String[] args) { // ①商品の初期化 VendingMachine vendingMachine = new VendingMachine(); // 購入最低金額の場合、追加入金 Scanner scanner = new Scanner(System.in); do { // ②入金 System.out.println("お金を入れて下さい。"); int amount = scanner.nextInt(); vendingMachine.deposit(amount); // ③金額チェック(最低購入金額) } while (!vendingMachine.hasMinDeposit()); // ④購入可能商品を表示 System.out.println(""); System.out.println("購入可能な商品です。"); vendingMachine.showAvailablePurchases(); // ⑤商品を選択 String itemName; do { System.out.println(""); System.out.println("商品名を入力して下さい。"); itemName = scanner.next(); } while (!vendingMachine.isAvailablePurchases(itemName)); vendingMachine.selectItem(itemName); scanner.close(); // ⑥課金機能 vendingMachine.charge(itemName); } } |
クラス分けの価値
多くのプログラムは、Getter/Setterによるデータ構造的な仕組みでできています。
しかしこれは仕様の理解の難しさと、バグの温床になっています。
クラス分けする本当の価値は、仕様の明確化と機能の部品化です。
そうすることで、バグが少ないシステム、修正が容易なシステムが作られるのです。
私自身、もっと成長したいものです。
Level4
Level4ではさらに一歩進んで、ドメイン駆動的な記述に修正します。
ドメインとは「業務領域」のことです。
つまり、実際にシステムが使う人が使っている言葉や業務内容で
プログラムを表現するというアプローチなのです。
今後、仕様の理解や保守性の高さからコーディングスキルとして
ドメイン駆動は高く評価されていくと私は考えています。
【Javaサンプル】自動販売機(Level4) ドメイン駆動
コメント