本篇用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){//假设取款机内钞票的面值是20int 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大学教程--(第三版)