S SmartDocs
系列: Angelia java 69 行 · 更新於 2026-09-27

Main.java

Angelia/Java/Week_6_7/code/03_inheritance_lsp/Main.java

/**
 * 03_inheritance_lsp 示範程式。
 *
 * 編譯與執行:
 *   javac *.java
 *   java -ea Main
 */
public class Main {

    /**
     * 這個函式是「以 SavingsAccount 的契約為前提」寫的:
     * 把餘額全部領出來——依契約,提領剛好等於餘額的金額一定成功,之後餘額為 0。
     */
    static void withdrawEverything(SavingsAccount acct) {
        int all = acct.getBalance();
        boolean ok = acct.withdraw(all);
        System.out.println("  提領 " + all + " → 成功? " + ok
                + ",餘額 = " + acct.getBalance());
        assert ok : "契約保證:提領等於餘額的金額必定成功";
        assert acct.getBalance() == 0 : "契約保證:領光之後餘額為 0";
    }

    public static void main(String[] args) {
        System.out.println("=== 1. 繼承與 super ===");
        SavingsAccount s = new SavingsAccount();
        s.deposit(100);                    // deposit 繼承自 BankAccount,沒有重寫
        System.out.println("  withdraw(30) → " + s.withdraw(30) + ",餘額 " + s.getBalance());
        System.out.println("  withdraw(500) → " + s.withdraw(500) + ",餘額 " + s.getBalance());
        assert s.getBalance() == 70;
        // BankAccount b = new BankAccount();   // 編譯錯誤:抽象類別不能實例化

        System.out.println("=== 2. 動態分派:陣列裡每個物件跑自己的 withdraw ===");
        BankAccount[] accounts = {
            new SavingsAccount(), new LoggingSavingsAccount(), new AccountWithWithdrawalFees()
        };
        for (BankAccount a : accounts) {
            a.deposit(100);
            boolean ok = a.withdraw(100);   // 靜態型別是 BankAccount,執行的卻是各子類別的版本
            System.out.println("  " + a.getClass().getSimpleName()
                    + ":存 100 領 100 → " + ok + ",餘額 " + a.getBalance());
        }

        System.out.println("=== 3. LoggingSavingsAccount:擴充而非取代 ===");
        LoggingSavingsAccount l = new LoggingSavingsAccount();
        l.deposit(50);
        l.withdraw(20);
        l.withdraw(999);
        System.out.println("  log = " + l.getLog());
        assert l.getLog().size() == 3;
        l.getLog().clear();                 // 改的是副本(防禦性複製)
        assert l.getLog().size() == 3 : "外界不該能改到內部 log";

        System.out.println("=== 4. LSP:合規的子型別可以放心替換 ===");
        SavingsAccount good = new LoggingSavingsAccount();
        good.deposit(80);
        withdrawEverything(good);           // OK

        System.out.println("=== 5. LSP:違規的子型別會讓正確的程式碼出錯 ===");
        SavingsAccount bad = new OverdraftSavingsAccount();
        bad.deposit(80);
        withdrawEverything(bad);            // 這一步沒問題……
        bad.withdraw(1000);                 // 但契約說不可能發生的事發生了:
        System.out.println("  再提 1000 → 餘額 = " + bad.getBalance() + "(負數!不變量被打破)");
        if (bad.getBalance() < 0) {
            System.out.println("  ★ 編譯器沒有抱怨,但以 SavingsAccount 契約為前提的程式碼全都可能出錯。");
        }
        System.out.println("全部完成。");
    }
}

相關文章