实现WoniuATM的注册与登录
基于前面所学知识,我们已经可以尝试完成WoniuATM模拟系统的基础功能了。那么本实验就重点来完成WoniuATM模拟系统的操作菜单绘制(基于命令行),以及注册和登录两个核心功能,并完成简单的函数(方法)调用。
试验简介
试验目的
1.掌握命令行菜单的绘制方法和调用方式。
2.利用数组完成用户的注册和登录。
3.对目前系统存在的问题有清楚的认识,并通过后续学习继续完善。
试验流程
1.设计程序的基本结构。
在程序设计中,我们不可能把所有的代码都放在main主方法中,这样可以想像当代码量多了以后是几乎不可能行得通的。不单无法维护,代码也无法重用,同时更不能对代码结构进行各种优化。所以,无论哪门程序设计语言,无论是面向对象还是面向过程的编程语言,都支持利用类或者函数(或方法)来针对不同的功能利用不同的函数(或方法)来处理,这样可以从根本上避免程序的结构性混乱。
所以,在完成一个程序设计之前,通常我们可以简单地先划分出程序要实现的小功能,并将每个小功能作为一个函数(或方法)来处理。在Java面向对象程序中,我们通常称之为方法。我们可以将本实验的功能细分为四个小功能:主菜单,子菜单,注册,登录,所以我们可以先规划如下代码结构:
package com.woniuxy.atm.one;
public class MainUI { public static void main(String[] args) { MainUI ui = new MainUI(); // 定义当前类的变量(实例)ui ui.mainMenu(); // 调用当前类的方法mainMenu() } // 绘制主操作菜单(登录前) public void mainMenu() { } // 绘制子操作菜单(登录后) public void subMenu() { } // 注册功能 public void register() { } //登录功能 public void login() { } } |
2.实现登录前的主菜单。
基于命令行的操作,其实主要通过输出一个菜单提示,然后根据用户的输入值进行对比和判断,利用分支结构去调用不同的功能即可实现,所以主菜单的实现功能代码如下:
// 绘制主操作菜单(登录前) public void mainMenu() { System.out.println("=====欢迎使用蜗牛ATM无限制存取款系统======="); System.out.println("===请输入你的选项,1:登录 2:注册 3:退出 ==="); System.out.println("=================================="); Scanner sc = new Scanner(System.in); String option = sc.next(); switch (option) { case "1": this.login(); // this: 代表本类的实例(只适用于非静态方法之间的调用) break; case "2": this.register(); break; case "3": System.exit(0); // 退出当前程序 break; default: System.out.println("你输入的选项错误,请重新输入。 "); this.mainMenu(); // 当输入错误的时候,通过递归调用自己,重新提示输入 break; } } |
3.实现登录成功后的子菜单。
// 绘制子操作菜单(登录后) public void subMenu() { System.out.println("====================请输入你的选项:==============="); System.out.println("==1:查询余额 2:转账 3:取款 4:存款 5:返回主菜单 6:退出=="); System.out.println("=============================================="); Scanner sc = new Scanner(System.in); String option = sc.next(); switch (option) { case "1": // 调用查询余额的方法 break; case "2": // 调用转账的方法 break; case "3": // 调用取款的方法 break; case "4": // 调用存款的方法 break; case "5": this.mainMenu(); // 返回主菜单 break; case "6": System.exit(0); // 退出 break; default: System.out.println("你输入的选项错误,请重新输入。 "); this.subMenu(); // 当输入错误的时候,通过递归调用自己,重新提示输入 break; } } |
4.利用数组实现注册功能。
要实现用户的注册功能,我们必须首先定义一个存储方式,用于保存用户注册的账户信息,进而在登录验证或账户处理过程中能够记录相应的操作。从目前我们所掌握的知识来说,只有数组是可以用来保存多组信息的,普通的变量无法完成这一目的。所以我们优先考虑使用数组来完成。
但是数组通常是用来保存一类数据类型的,我们要实现注册功能,或者说一个账户信息至少应该包含三个信息:“用户名,密码,余额”,这还不包括其它账户信息,比如联系方式,真实姓名等等。我们不妨先简化该模型,简化为就三个关键信息:“用户名,密码,余额”。那么现在的问题关键点就在于,如果利用数组来更好地完成这三个信息的处理,进行多账户管理呢?
我们可以有三种方式来完成:
(1) 分别为三个关键信息定义三个数组,保持数组下标和数组长度的充分一致性,这样每个下标对应的信息就是同一个账户的信息。
(2) 定义一个二维数组,数组当中的每一维对应一个账户信息,里面包含三个关键信息。
(3) 使用面向对象的失血模型定义一个数据类,通过该类的属性来保存其信息,每一个账户对应一个数据类实例,通过数组或列表进行管理。(此方法需要完成面向对象的知识学习后方可理解)。
但是无论以哪种方式来实现,我们可以看到,数组在其中都扮演着重要的作用。同时,由于在Java中数组的长度是固定的,不可变的,所以我们要么使用先预设固定长度的方式将就用,要么采用数组动态扩展的方式均可,这一部分知识,前面我们已经做过讲解。
下面我们来看看利用三个数组来完成用户注册,并为该账户设置初始余额5000元的实现代码:
// 注册功能 public void register() { // 设置数组的初始长度为10,表示可注册10个账户 String[] users = new String[10]; String[] passes = new String[10]; int[] balance = new int[10]; Scanner sc = new Scanner(System.in); System.out.println("请输入你的用户名:"); String username = sc.next(); System.out.println("请输入你的密码:"); String password = sc.next(); users[0] = username; passes[0] = password; balance[0] = 5000; System.out.println("恭喜你,注册开户成功,请选择操作: 1.登录 2.注册 3.返回."); String option = sc.next(); switch(option) { case "1": this.login(); break; case "2": this.register(); break; case "3": this.mainMenu(); break; default: this.mainMenu(); break; } } |
我们来分析一下,上述代码存在哪些问题:
(1) 由于我们将三个数组定义在方法体register()内部,所以每一次调用该方法,数组都会被重新初始化,所以我们的注册功能虽然看上去逻辑上没有问题,但是事实上是无效的。
(2) 由于我们每次注册都在给三个数组的下标【0】赋值,那么相当于我们从来都是在注册一个账户而已,显然这也是没有实际意义的。
(3) 注册账户本身不是目的,账户注册后我们需要登录,需要完成存取款等操作,这才是有价值的功能。但是如果我们把这三个数组定义在方法体内部(称为局部变量),在其它方法体里面是无法读取到其内容的。那么,登录如何获取和验证呢?我们不可能在登录方法体login()内部又重新定义三个数组,否则注册的账户信息跟登录的账户信息根本就不是同一处来源,没有意义。
(4) 注册方法体内还缺乏一个基本的校验,那就是如果要注册的账户信息已经存在,那么就不应该继续接受注册,而应该提醒用户该账户信息已经存在,不允许重复注册。
那么,要如何解决这些问题呢?
5.实现登录功能验证。
我们假定账户信息已经保存在数组中,那么如何实现登录的验证呢?核心代码如下:
public void login() { // 此处的定义只是为了展示代码逻辑,无法真正实现登录 String[] users = new String[10]; String[] passes = new String[10]; Scanner sc = new Scanner(System.in); System.out.println("请输入你的用户名:"); String username = sc.next(); boolean isUserOk = false; int index = -1; for (int i=0; i<users.length; i++) { if (username.equals(users[i])) { // 一旦用户名存在,则退出循环 isUserOk = true; index = i; break; } } if (isUserOk == false) { System.out.println("你输入的用户名不正确,请重新输入."); this.login(); } System.out.println("请输入你的密码:"); String password = sc.next(); if (password.equals(passes[index])) { System.out.println("恭喜你,登录成功,现在进入操作菜单。"); this.subMenu(); } else { System.out.println("请输入的密码不正确,请重新输入."); this.login(); } } |
当然,同样的问题,注册的账户信息和登录的账户信息必须保存在同一个数组中。这一问题请读者朋友与上一个问题一起解决即可。
6.利用二维数组实现注册。
public void register2() { String[][] accounts = new String[10][3]; Scanner sc = new Scanner(System.in); System.out.println("请输入你的用户名:"); String username = sc.next(); System.out.println("请输入你的密码:"); String password = sc.next(); accounts[0][0] = username; accounts[0][1] = password; accounts[0][2] = "5000";// 由于数组类型为字符串型,所以余额也需要加上双引号 System.out.println("恭喜你,注册开户成功,请选择操作: 1.登录 2.注册 3.返回."); String option = sc.next(); switch(option) { case "1": this.login(); break; case "2": this.register(); break; case "3": this.mainMenu(); break; default: this.mainMenu(); break; } } |
通过上述代码,我们也可以看到,由于数组是字符串类型,所以本来是整数的余额也必须强制加上双引号以保持类型的匹配,这显然又产生了一个新的问题。同时,对于登录验证来说,其操作方式类似。但是之前存在的问题依然存在,我们必须要通过优化代码,解决上述问题,进而才可能继续往下实现代码功能。
思考练习
1.请利用数组完善用户的注册与登录功能。
2.请尝试完成用户的查询全额和转账的功能。
3.请总结上述代码的实现存在哪些问题。