2017年4月15日 星期六

ATM 04

上一篇我們用了JAVA annotation實現meta programming,不知道那是什麼的可以看這裡https://en.wikipedia.org/wiki/Metaprogramming

重點是,那不重要
讓我們看看User類別,很顯然的ID、password以及log都與User關聯甚低,他們應該屬於另一層次,所以我們實作帳號型別以存放他們
class Account {
    TransactionLogger log = new TransactionLogger();

    String ID;
    String password;
    Account(String ID, String password) {
        this.ID = ID;
        this.password = password;
    }
}
Account ac;
User(String ID, String password) {
    ac = new Account(ID, password);
}
注意我用的是inner class,接著我們得改一些地方才能使用新的做法,但我不會在這裡寫,因為只是改改參考方式
現在我們看到我們的帳戶沒有餘額這個欄位,所以我們要補上
然後我還新增了一個構建子做為選擇

接著讓我們將提存款流程補完整
@IfChoose(value = 1)
void deposit() {
    System.out.println("請輸入存款金額:");
    int amountOfDeposit = Main.sc.nextInt();

    ac.balance += amountOfDeposit;
    ac.log.AddDepositLog(amountOfDeposit);
}
@IfChoose(value = 2)
void withdraw() {
    System.out.println("請輸入提款金額:");
    int amountOfWithdraw = Main.sc.nextInt();

    if (amountOfWithdraw < ac.balance) {
        ac.balance -= amountOfWithdraw;
        ac.log.AddWithdrawLog(amountOfWithdraw);
    } else {
        System.out.println("操作失敗,餘額不足");
    }
}
這是他們全新的定義,我們檢查了可能的失敗好提醒使用者發生了什麼,並且拒絕交易

接著我們把副作用與使用者介面分開
ac.userDeposit(amountOfDeposit); // In deposit method
就像這樣,好讓我們可以寫測試程式,由於記錄器已經有驗證程式了,所以我們把焦點放在帳戶餘額身上
@Test
public void depositTest() {
    User u = new User("", "", 50000);
    u.ac.userDeposit(10000);

    Assert.assertEquals(60000, u.ac.balance, 0.0000001);
}
另一個測試長得差不多

測試通過了之後呢
讓我們把焦點移到難看的帳號驗證部分
for (User u : Users) {
    if (u.ac.ID.equals(userID) && u.ac.password.equals(userPassword)) {
        // ...
    }
}
可以看得出來,這很難看懂,所以我們動手改一改
for (User u : Users) {
    if (u.ac.ID.equals(userID) && u.ac.password.equals(userPassword)) {
        AccountExist = true;
        which = Users.indexOf(u);
        break;
    }
}
這裡判斷是否存在這個帳號
if (AccountExist) {
    System.out.println("請選擇功能,\n[1] 存款\n[2] 提款");
    int choose = sc.nextInt();

    for (Method m : Users.get(which).getClass().getDeclaredMethods()) {
        IfChoose c = m.getAnnotation(IfChoose.class);
        if (choose == c.value()) {
            m.invoke(Users.get(which));
            break;
        }
    }
}
操作就移到後面,這樣看來只是更複雜,所以我們必須提煉出函數來表明意圖
static boolean exist(String userID, String userPassword) {
    for (User u : Users) {
        if (u.ac.ID.equals(userID) && u.ac.password.equals(userPassword)) {
            which = Users.indexOf(u);
            return true;
        }
    }
    return false;
}
為了這麼做,which被改為Main的static變數

這時候我們已經能看出,Users的需求已足以令我們為其準備一個抽象層了,所以我們要從Main中分離出它的部分
第一步很容易決定,Users必須擁有自己的操作類別
public class DataBase {
    static List<User> Users = new ArrayList<>();

    static ResultAccount find(String userID, String userPassword) {
        ResultAccount answer = new ResultAccount();
        for (User u : DataBase.Users) {
            if (u.ac.ID.equals(userID) && u.ac.password.equals(userPassword)) {
                answer.location = DataBase.Users.indexOf(u);
                answer.found = true;
                return answer;
            }
        }
        answer.found = false;
        return answer;
    }
}

class ResultAccount {
    boolean found = false;
    int location = -1;
}

ResultAccount account = DataBase.find(userID, userPassword);
if (account.found) {
    System.out.println("請選擇功能,\n[1] 存款\n[2] 提款");
    int choose = sc.nextInt();

    for (Method m : DataBase.Users.get(account.location).getClass().getDeclaredMethods()) {
        IfChoose c = m.getAnnotation(IfChoose.class);
        if (choose == c.value()) {
            m.invoke(DataBase.Users.get(account.location));
            break;
        }
    }
}
上面的步驟是 Users移入DataBase => exist改為find => 為了不存在的which,建立ResultAccount型別 => 改變使用方式
這下我們得以建立對Users的測試案例
public class DataBaseTest {
    @Test
    public void findUser() {
        DataBase.Users.add(new User("1", "1", 50000));
        ResultAccount an = DataBase.find("1", "1");

        Assert.assertEquals(true, an.found);
        Assert.assertEquals(0, an.location);
    }
    @Test
    public void notFoundUser() {
        DataBase.Users.add(new User("1", "1", 50000));
        ResultAccount an = DataBase.find("2", "1");

        Assert.assertEquals(false, an.found);
        Assert.assertEquals(-1, an.location);
    }
}
全數通過,非常不錯

這篇就到這裡,下一篇我們將實現完整的流程

NEXT: ATM 05

沒有留言:

張貼留言