本篇用C#實現ATM自動取款機的一些功能。面臨的第一個問題是:如何把與自動取款機相關的有形的、無形的方面抽象出來。大致如下:
(1)關於用戶帳號的類:Account
(2)關於銀行數據庫的類:BankDatabase
(3)關於ATM屏幕顯示的類:Screen
(4)關於ATM鍵盤的類:Keypad
(5)關於進鈔、出鈔口的類:DepositSlot
(6)關於ATM出錢的類:CashDispendser
(7)關於事務的基類:Transaction
(8)關於查詢的事務類:BalanceInquiry
(9)關於取款的事務類:Withdrawl
(10)關於存款的事務類:Deposit
(11)關於ATM本身的類:ATM
(12)運行
(1)關於用戶帳號的類:Account
該類包含與卡號、密碼、可用余額、總余額相關的字段和屬性,比提供了存款和取款的方法。
namespace MyATM
{/// <summary>
/// 用戶帳號
/// </summary>
public class Account{private int accountNumber; //卡號private int pin;//用來驗證private decimal availableBalance;//可用余額private decimal totalBalance;//總余額public Account(int theAccountNumber, int thePIN, decimal theAvailableBalance, decimal theTotalBalance){accountNumber = theAccountNumber;pin = thePIN;availableBalance = theAvailableBalance;totalBalance = theTotalBalance;}//卡號 只讀屬性
public int AccountNumber{get { return accountNumber; }}//可提取余額 只讀屬性
public decimal AvailableBalance{get { return availableBalance; }}//總余額 只讀屬性
public decimal TotalBalance{get { return totalBalance; }}//驗證輸入密碼是否正確
public bool ValidatePIN(int userPIN){return (userPIN == pin);
}//存款
public void Credit(decimal amount){totalBalance += amount;}//取款
public void Debit(decimal amount){availableBalance -= amount;totalBalance -= amount;}}}
(2)關於銀行數據庫的類:BankDatabase
該類維護着一個Account類型的數組,並提供驗證用戶,查詢余額,存款、取款等方法。
namespace MyATM
{/// <summary>
/// 銀行數據庫
/// </summary>
public class BankDatabase{private Account[] accounts;
public BankDatabase()
{accounts = new Account[2];
accounts[0] = new Account(12345,54321,1000.00M, 1200.00M);
accounts[1] = new Account(98765, 56789, 200.00M, 200.00M);
}//根據用戶銀行卡號獲取該用戶帳號
private Account GetAccount(int accountNumber){foreach (Account currentAccount in accounts){if (currentAccount.AccountNumber == accountNumber)
{return currentAccount;
}}return null;}//驗證用戶,根據卡號和密碼
public bool AuthenticateUser(int userAccountNumber, int userPIN){//先根據卡號獲取帳號
Account userAccount = GetAccount(userAccountNumber);if (userAccount != null){return userAccount.ValidatePIN(userPIN);
}else
{return false;}}//返回可提取的余額,根據卡號
public decimal GetAvailableBalance(int userAccountNumber){Account userAccount = GetAccount(userAccountNumber);return userAccount.AvailableBalance;
}//返回所有余額
public decimal GetTotalBalance(int userAccountNumber){Account userAccount = GetAccount(userAccountNumber);return userAccount.TotalBalance;
}//給用戶存款
public void Credit(int userAccountNumber, decimal amount){Account userAccount = GetAccount(userAccountNumber);userAccount.Credit(amount);}//給用戶取款
public void Debit(int userAccountNumber, decimal amount){Account userAccount = GetAccount(userAccountNumber);userAccount.Debit(amount);}}}
(3)關於ATM屏幕顯示的類:Screen
該類提供了分行顯示、不分行顯示、顯示金額這3個方法。
using System;
namespace MyATM
{/// <summary>
/// 屏幕
/// </summary>
public class Screen{//顯示不分行的信息
public void DisplayMessage(string message){Console.Write(message);}//顯示分行的信息
public void DisplayMessageLine(string message){Console.WriteLine(message);}//顯示金額
public void DisplayDollarAmmount(decimal amount){Console.Write("{0:c}", amount);
}}}
(4)關於ATM鍵盤的類:Keypad
該類的職責很明確,就是把輸入的數字返回。
using System;
namespace MyATM
{/// <summary>
/// 輸入鍵盤
/// </summary>
public class Keypad{//根據用戶輸入,返回一個整型
public int GetInput(){return Convert.ToInt32(Console.ReadLine());
}}}
(5)關於進鈔、出鈔口的類:DepositSlot
該類主要是確認進鈔、出鈔口是否收到錢,默認返回true。
namespace MyATM
{/// <summary>
/// 存款槽
/// </summary>
public class DepositSlot{//判斷是否收到錢
public bool IsMoneyReceived(){return true;}}}
(6)關於ATM出錢的類:CashDispendser
就像在現實生活中,ATM中肯定會預先存放一些人民幣,出錢的時候首先要判斷余額是否足夠,如果足夠就把ATM中當前的票數做適當的減法。
namespace MyATM
{/// <summary>
/// ATM取款
/// </summary>
public class CashDispendser{private const int INITIAL_COUNT = 500;//初始票數private int billCount;//當前取款機內票數public CashDispendser()
{billCount = INITIAL_COUNT;}//出錢
public void DispenseCash(decimal amount){int billsRequired = ((int)amount) / 20;billCount -= billsRequired;}//判斷是否有余額
public bool IsSufficientCashAvailable(decimal amount){//假設取款機內鈔票的面值是20
int billsRequired = ((int) amount)/20;return (billCount >= billsRequired);
}}}
(7)關於事務的基類:Transaction
我們可以回想一下,現實生活中,ATM的主要功能包括:查詢余額,取款,存款等。雖然執行的過程不盡相同,但所有的這些事務包含相同的部分:比如說,必須有屏幕必須針對卡號一定和數據庫打交道,等等。於是,我們先抽象出一個有關事務的基類,這個基類是不需要被實例化的,所以把它定義成抽象類。如下:
namespace MyATM
{/// <summary>
/// ATM事務
/// </summary>
public abstract class Transaction{private int accountNumber;//卡號private Screen userScreen;//屏幕private BankDatabase database;//銀行數據庫public Transaction(int userAccount, Screen theScreen, BankDatabase theDatabase){accountNumber = userAccount;userScreen = theScreen;database = theDatabase;}//銀行卡號屬性 只讀
public int AccountNumber{get { return accountNumber; }}//用戶使用的屏幕屬性 只讀
public Screen UserScreen
{get { return userScreen; }}//用戶使用的數據庫 只讀
public BankDatabase Database
{get { return database; }}//抽象方法 子類必須重寫
public abstract void Execute();}}
以上,在其它有關事務的派生類中都可以訪問到基類的只讀屬性,並且子類必須重寫抽象基類的Execute方法。
(8)關於查詢的事務類:BalanceInquiry
該類調用Database類的方法查詢可用余額和總余額。
namespace MyATM
{/// <summary>
/// ATM余額查詢事務
/// </summary>
public class BalanceInquiry : Transaction{public BalanceInquiry(int userAccountNumber, Screen atmScreen, BankDatabase atmBankDatabase) : base(userAccountNumber, atmScreen, atmBankDatabase){}public override void Execute(){//獲取可用余額
decimal availableBalance = Database.GetAvailableBalance(AccountNumber);
//獲取總余額
decimal totalBalance = Database.GetTotalBalance(AccountNumber);
//打印信息
UserScreen.DisplayMessageLine("\n余額信息為:");
UserScreen.DisplayMessage(" -可用余額為:");
UserScreen.DisplayDollarAmmount(availableBalance);UserScreen.DisplayMessage("\n -總余額為:");
UserScreen.DisplayDollarAmmount(totalBalance);UserScreen.DisplayMessageLine("");}}}
(9)關於取款的事務類:Withdrawl
當用戶輸入取款的金額,該類必須要做的事情是:在用戶的銀行數據庫中和ATM上做相應的減法,還必須考慮什么時候退出循環,用戶是否按了取消鍵,用戶賬戶上是否有余額,以及ATM中是否有余額。
namespace MyATM
{/// <summary>
/// ATM取款事務
/// </summary>
public class Withdrawl : Transaction{private decimal amount;//取款金額private Keypad keypad;//鍵盤private CashDispendser cashDispenser;//出錢private const int CANCELED = 6;//對應菜單中的取消public Withdrawl(int userAccountNumber, Screen atmScreen, BankDatabase atmBankDatabase, Keypad atmKeypad,CashDispendser atmCashDispenser) : base(userAccountNumber, atmScreen, atmBankDatabase)
{keypad = atmKeypad;cashDispenser = atmCashDispenser;}public override void Execute(){bool cashDispensed = false; //表示還沒出錢bool transactionCanceled = false; //表示不取消事務do
{int selection = DisplayMenuOfAmounts();
if (selection != CANCELED)//如果用戶沒有按取消{amount = selection; //確定取款金額
//根據卡號獲取可用余額
decimal availableBalance = Database.GetAvailableBalance(AccountNumber);
if (amount <= availableBalance)//如果取款金額小於可用余額{if (cashDispenser.IsSufficientCashAvailable(amount))//如果ATM余額足夠{Database.Debit(AccountNumber, amount);//賬戶扣款
cashDispenser.DispenseCash(amount);//ATM扣款
cashDispensed = true;//跳出循環UserScreen.DisplayMessageLine("\n您可以拿着錢離開了~~");
}else//如果ATM余額不夠{UserScreen.DisplayMessageLine("\n ATM余額不足." + "\n\n請提取更小的金額~~");}}else
{UserScreen.DisplayMessageLine("\n 賬戶余額不足." + "\n\n請提取更小的金額~~");}}else //如果用戶按了取消,提示正在退出並跳出循環{UserScreen.DisplayMessageLine("\n正在取消......");
transactionCanceled = true;
}} while ((!cashDispensed) && (!transactionCanceled));
}/// <summary>
/// 顯示提款金額
/// </summary>
/// <returns></returns>
private int DisplayMenuOfAmounts(){int userChoice = 0; //默認提款金額int[] amounts = {0, 20, 40, 60, 100, 200};
while (userChoice ==0)
{//顯示菜單
UserScreen.DisplayMessageLine("\nWithdrawal options:");
UserScreen.DisplayMessageLine("1-20元");
UserScreen.DisplayMessageLine("2-40元");
UserScreen.DisplayMessageLine("3-60元");
UserScreen.DisplayMessageLine("4-100元");
UserScreen.DisplayMessageLine("5-200元");
UserScreen.DisplayMessageLine("6-取消操作");
UserScreen.DisplayMessage("\n輸入數字(1-6),選擇選項:");
int input = keypad.GetInput();
switch (input)
{case 1: case 2: case 3: case 4: case 5:userChoice = amounts[input];break;
case CANCELED:
userChoice = CANCELED;break;
default:
UserScreen.DisplayMessageLine("\n輸入無效數,請重試~~");
break;
}}return userChoice;
}}}
以上,
維護的amount變量表示的是取款金額,在每次用戶輸入提款金額后為該變量賦值。
Keypad類型的變量kepad和CashDispendser類型的變量cashDispenser需要在構造函數中為其賦初值,而這2個因素是在取款時特有的,在事務的抽象基類中不需要考慮這2個因素。
通過DisplayMenuOfAmounts方法,會向用戶顯示一些面值,以及對應的數字鍵,然后根據用戶按下的數字鍵返回對應的、int類型的面值。
在Execute方法中,首先循環的2個條件是用戶沒有按取消鍵和還沒出錢的時候。然后把DisplayMenuOfAmounts方法的返回值賦值給表示取款金額的amount變量,據此判斷用戶賬戶的余額是否足夠,判斷ATM的余額是否足夠,最后在用戶賬戶和ATM中分別扣款。這期間,如果用戶按了取消鍵,就把表示取消事務的變量transactionCanceled設置為true以跳出循環,完成扣款后把表示扣款完成的變量cashDispensed設置為true以跳出循環。
(10)關於存款的事務類:Deposit
該類最終是使用Database屬性把用戶輸入的金額保存到用戶賬戶上。另外需要考慮的是:用戶在存款的時候是否按了取消鍵。
namespace MyATM
{/// <summary>
/// ATM存款事務
/// </summary>
public class Deposit : Transaction{private decimal amount;private Keypad keypad;
private DepositSlot depositSlot;
private const int CANCELED = 0;public Deposit(int userAccountNumber, Screen atmScreen, BankDatabase atmBankDatabase, Keypad atmKeypad,DepositSlot atmDepositSlot) : base(userAccountNumber, atmScreen, atmBankDatabase)
{keypad = atmKeypad;depositSlot = atmDepositSlot;}public override void Execute(){//確定存款金額
amount = PromptForDepositAmount();if (amount != CANCELED)
{UserScreen.DisplayMessage("\n請輸入的存款金額為" + amount);
//確認是否收到錢
bool isReceived = depositSlot.IsMoneyReceived();
if (isReceived)
{UserScreen.DisplayMessageLine("\n存款成功~~");
Database.Credit(AccountNumber, amount);//存款到賬戶
}else
{UserScreen.DisplayMessageLine("\n存款時發生錯誤~~");
}}else
{UserScreen.DisplayMessageLine("\n正在取消交易......");
}}/// <summary>
/// 顯示存款金額
/// </summary>
/// <returns></returns>
private decimal PromptForDepositAmount(){UserScreen.DisplayMessage("\n請輸入存款金額(輸入0退出)");
int input = keypad.GetInput();
if (input == CANCELED)
{return CANCELED;
}else
{return input;
}}}}
以上,
私有方法PromptForDepositAmount用來返回用戶輸入的金額,如果用戶按取消鍵,就返回0。
(11)關於ATM本身的類:ATM
該類主要是提供給外部一個方法用來運行。
namespace MyATM
{public class ATM{private bool userAuthenticated;//表示用戶是否驗證通過private int currentAccountNumber;//當前交易的銀行卡號private Screen screen;//屏幕private Keypad keypad;//鍵盤private CashDispendser cashDispendser;//出款private DepositSlot depositSlot;//存款private BankDatabase bankDatabase;//數據庫//菜單選項枚舉
private enum MenuOption{BANLANCE_INQUIRY = 1,//余額查詢
WITHDRAWAL = 2,//取款
DEPOSIT = 3,//存款
EXIT_ATM = 4//退出
}public ATM()
{userAuthenticated = false;//默認驗證不通過currentAccountNumber = 0;//默認卡號
screen = new Screen();//默認屏幕keypad = new Keypad();//默認鍵盤cashDispendser = new CashDispendser();//默認出款幫助類bankDatabase = new BankDatabase();//默認銀行數據庫depositSlot = new DepositSlot();//默認存款幫助類}//運行
public void Run(){while (true){while (!userAuthenticated)//如果用戶沒有驗證通過,就一直循環{screen.DisplayMessageLine("\n歡迎");
AuthenticateUser();PerormTransactions();//重新設置一些參數
userAuthenticated = false;
currentAccountNumber = 0;screen.DisplayMessageLine("\n謝謝,再見~~");
}}}//驗證用戶
private void AuthenticateUser(){screen.DisplayMessage("\n請輸入卡號");
int accountNumber = keypad.GetInput();
screen.DisplayMessage("\n輸入密碼");
int pin = keypad.GetInput();
userAuthenticated = bankDatabase.AuthenticateUser(accountNumber, pin);if (userAuthenticated)
{currentAccountNumber = accountNumber; //保存當前的用戶卡號
}else
{screen.DisplayMessageLine("無效的卡號或密碼,請重試~~");
}}//執行交易
private void PerormTransactions(){Transaction currenTransaction;bool userExited = false; //用戶還沒選擇退出while (!userExited)
{//確定選擇的具體事務
int mainMenuSelction = DisplayMainMenu();
switch ((MenuOption)mainMenuSelction)
{case MenuOption.BANLANCE_INQUIRY:
case MenuOption.WITHDRAWAL:
case MenuOption.DEPOSIT:
currenTransaction = CreateTransaction(mainMenuSelction);currenTransaction.Execute();break;
case MenuOption.EXIT_ATM:
screen.DisplayMessageLine("\n正在退出系統......");
userExited = true;//退出循環break;
default:
screen.DisplayMessageLine("\n無效選項,請重新選擇~~");
break;
}}}//顯示菜單
private int DisplayMainMenu(){screen.DisplayMessageLine("\n主菜單:");
screen.DisplayMessageLine("1-查詢余額");
screen.DisplayMessageLine("2-提取現金");
screen.DisplayMessageLine("3-存款");
screen.DisplayMessageLine("4-退出\n");
screen.DisplayMessage("請輸入選擇:");
return keypad.GetInput();
}//創建交易
private Transaction CreateTransaction(int type){Transaction temp = null;
switch ((MenuOption)type)
{case MenuOption.BANLANCE_INQUIRY:
temp = new BalanceInquiry(currentAccountNumber, screen, bankDatabase);
break;
case MenuOption.WITHDRAWAL:
temp = new Withdrawl(currentAccountNumber, screen, bankDatabase, keypad, cashDispendser);
break;
case MenuOption.DEPOSIT:
temp = new Deposit(currentAccountNumber, screen, bankDatabase, keypad, depositSlot);
break;
}return temp;
}}}
以上,
向外部提供了一個Run方法,客戶端只要調用該實例方法就可以了。在Run方法內部又實現了對用戶的驗證和進行用戶選擇的事務。
私有方法DisplayMainMenu用來顯示主菜單項,並返回用戶的選擇。
在PerormTransactions方法中,根據用戶的選擇,使用CreateTransaction(int type)方法創建具體的事務,並最終執行。並需要考慮用戶按退出按鈕的情況。
(12)運行
using System;
namespace MyATM
{class Program
{static void Main(string[] args){ATM theATM = new ATM();
theATM.Run();Console.ReadKey();}}}
總結:ATM案例很好地體現了面向對象的一些特點,尤其是:當我們面對一個看似復雜的案例時,首先需要一種對有形和無形事物抽象的能力,其次要盡可能地把代碼中一些重復的部分提煉到基類中去,就像本案例中有關事務的抽象基類。
參考資料:
Visual C# 2008大學教程--(第三版)