本篇用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大学教程--(第三版)

C#实现ATM自动取款机的更多相关文章

  1. 语言模拟ATM自动取款机系统

    C语言实验报告       题目名称:C语言模拟ATM自动取款机系统 C语言模拟实现ATM自动取款机功能:输入密码,余额查询,取款,存款,转账,修改密码,退出功能: 代码实现的功能: 账号及密码输入: ...

  2. ATM自动取款机程序感想

    上周四的Java考试,老师并没有我们考暑假给我们布置的样卷的java程序,而是让我们做一个设计ATM的程序,然而这个对于我们来说好难,因为暑假没有学好java,首先基础知识还没有掌握,输入数据一开始都 ...

  3. linux 下用C实现 ATM 自动取款机功能 (进程间通信)

    直接先上图: 项目需求: 主要分为两人大模块: 客户端 .进入时的功能开户.销户.登录.解锁 开户:输入姓名.身份证号.设置密码,如果开户成功,则服务器上保存一个账号信号(一个账号存一个文件,文件名建 ...

  4. 5、原生jdbc链接数据库实例-自动取款机

    ATM自动取款机需求 一.登陆 1.界面要求:服务选择 1.老用户登陆:进入后输入卡号密码登陆 2.新用户开户:开户需要输入身份证号,记录姓名,开户时间.然后机器给出卡号,原始密码:111111. 卡 ...

  5. [CareerCup] 12.6 Test an ATM 测试一个自动取款机

    12.6 How would you test an ATM in a distributed banking system? 这道题问我们如何来测试一个自动取款机,我们首先要询问下列问题: - 谁来 ...

  6. NEYC 2017 自动取款机 atm Day6 T1

                                                                                          自动取款机 [问题描述] 小 ...

  7. 洗礼灵魂,修炼python(77)--全栈项目实战篇(5)—— ATM自动存取机系统

    要求: 1.完成常识中的ATM存取款机功能 2.把ATM机故障考虑进去 3.不能直接输入账户名和卡号等等信息,模拟出插银行卡让ATM机自动读取卡信息 4.密码验证超过三次错误即锁定账户 5.操作类型有 ...

  8. 你怎么改造和重新设计一个ATM银行自动取款机

  9. MySQL银行ATM存取款机系统(需求分析)

    银行ATM需求文档 一.E-R图形文 二.开发步骤 1.明确需求--数据库设计--编码实现功能--测试 2.绘制e-r图--绘制数据库模型图--使用三大方式规范数据库结构 三.开发思路 1. 模型图综 ...

随机推荐

  1. wpf tooltip 样式自定义

    <Style BasedOn="{StaticResource {x:Type ToolTip}}" TargetType="ToolTip"> & ...

  2. P2184 【贪婪大陆】

    看到全是线段树或者树状数组写法,就来提供一发全网唯一cdq分治三维偏序解法吧 容易发现,这个题的查询就是对于每个区间l,r,查询有多少个修改区间li,ri与l,r有交集 转化为数学语言,就是查询满足l ...

  3. Effective STL 学习笔记 Item 16:vector, string & C API

    有时需要支持 C 的接口,但这并不复杂. 对于 vector 来讲, \(v[0]\) 的地址 \(\&v[0]\) 即可作为数组指针传递给 C API: 1: // Legacy C API ...

  4. hdu 1171 有num1个w1 , num2个w2 ……. (母函数)

    输入n,代表学院里面有n种设备,并且在下面输入n行,每一行输入v,m代表设备的价格为v,设备的数量是m.然后要求把这些设备的总价值分摊,尽量平分,使其总价值接近相等,最好是相等 比如样例二(1+X10 ...

  5. HP 打印机监控

    http://www.ttlsa.com/zabbix/zabbix-monitor-hp-printer/ https://www.cnblogs.com/losbyday/articles/583 ...

  6. mysql千万级表关联优化(2)

    概述: 交代一下背景,这算是一次项目经验吧,属于公司一个已上线平台的功能,这算是离职人员挖下的坑,随着数据越来越多,原本的SQL查询变得越来越慢,用户体验特别差,因此SQL优化任务交到了我手上. 这个 ...

  7. CRLF LF CR

    The Carriage Return (CR) character (0x0D, \r) moves the cursor to the beginning of the line without ...

  8. Kibana部署及配置(四)

    一.Kibana安装 Kibana 是为 Elasticsearch 设计的开源分析和可视化平台.你可以使用 Kibana 来搜索,查看存储在 Elasticsearch 索引中的数据并与之交互.你可 ...

  9. CentOS 7下安装Python3.6和pip

    一.安装python3.6 1.1.安装python3.6需要依赖包 yum install openssl-devel bzip2-devel expat-devel gdbm-devel read ...

  10. 统计无向图中三角形的个数,复杂度m*sqrt(m).

    统计无向图中三角形的个数,复杂度m*sqrt(m). #include<stdio.h> #include<vector> #include<set> #inclu ...