C#实现ATM自动取款机

本篇用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();

        }

    }

}

1

 

总结:ATM案例很好地体现了面向对象的一些特点,尤其是:当我们面对一个看似复杂的案例时,首先需要一种对有形和无形事物抽象的能力,其次要尽可能地把代码中一些重复的部分提炼到基类中去,就像本案例中有关事务的抽象基类。


参考资料:
Visual C# 2008大学教程--(第三版)   

你可能感兴趣的:(C#)