经过了系统的学习了javase的基础,这个练习对我来说是真的深刻的体会到了对java来说“万物皆对象”这句话。这也是第一次认识到java这门语言在书写上面向对象与c语言的面向过程的不同之处。
对于图书管理系统来说,从整体上讲可以划分为四个部分。<
1.User对象(根据用户名不同,角色不同【管理员,普通用户】,以多态的形式来加以体现)。
2.进入命令循环。
(1) 打印操作菜单,供用户选择。
(2) 操作台读取用户的选择内容,以Executable 对象来体现。(以多态形式加以体现)
(3)执行命令(管理员与普通用户执行的方法不同,所有方法最终的执行,是由对象类型决定的)
import book.Book;
import book.BookStorage;
import command.IExecutable;
import input.Input;
import input.QuitException;
import user.User;
import user.UserStorage;
public class Main {
public static void main(String[] args) {
// 实例化一个负责处理输入的对象
Input input = new Input();
// 我们的书架对象,在应用运行过程中,只需要一份
BookStorage bookStorage = new BookStorage();
try {
UserStorage userStorage1= new UserStorage();
User user = userStorage1.login(input);
while (true) {
/* 打印用户角色对应的菜单,并且让用户选择
execute : 执行
executable : 具备可以执行的能力
IExecutable : 计划用接口去表示*/
IExecutable command = input.actionMenu(user);
// 根据用户的选择,执行对应的操作命令
command.execute(bookStorage, user, input);
}
} catch (QuitException exc) {
}
System.out.println("欢迎下次使用");
}
}
(1)负责用户在操作台上进行登录操作。
(2)打印操作菜单,供用户选择。(用户不同,操作菜单不同)
package input;
import command.IExecutable;
import user.User;
import java.util.Scanner;
public class input {
//用户登录
private final Scanner scanner = new Scanner(System.in);
public String Prompt(String prompt){
System.out.println(prompt+":");
if(!scanner.hasNextLine()){
// 用户按下 Ctrl + D 了
// 说明用户想退出了
// 通过异常的方式,向外通知
throw new RuntimeException();
}
return scanner.nextLine();
}
// 打印操作菜单,供用户选择(用户不同,操作菜单不同)
public IExecutable actionMenu(User user){
IExecutable[] supportedCommands = user.getSupportedCommands();
//supportedCommands 是对象类型的数组。
while(true){ //获取不同用户 所支持的不同功能
showMenu(supportedCommands); //打印菜单
String str= Prompt("请选择您要进行的操作:"); //将用户输入的内容赋值给str变量
int select = Integer.parseInt(str); //将字符串参数作为有符号的十进制整数进行解析
//比较用户输入字符选择命令是否正确
if(select>=1&&select<=supportedCommands.length)
{
return supportedCommands[select - 1];
}
}
}
public void showMenu( IExecutable[] supportedCommands ){
//依次打印用户所支持的命令,打印每个命令的名称,显示操作菜单
for(int i = 0;i
(1)对于用户来说分为普通用户与管理员。
先创建一个抽象类User,让CommonUser与AdminUser(管理员)继承User的子类均重写User内的方法以实现区分普通用户与管理员的目的。
抽象类 User:
package user;
import command.IExecutable;
public abstract class User {
private String username;
public User(String username) { //构造方法
this.username = username;
}
public String getUsername() { //get 方法
return username;
}
/*
作为抽象的类,用户,不知道自己的角色
所以也完全不知道支持哪些命令
应该定义成抽象方法,供子类去实现*/
public abstract IExecutable[] getSupportedCommands();
}
CommandUser类:
package user;
import command.IExecutable;
public class CommonUser extends User {
public CommonUser(String username) {
super(username);
}
@Override
public IExecutable[] getSupportedCommands() {
return new IExecutable[]{
//支持普通用户实现的功能
};
}
}
(2)对于一个用户如何区分为普通用户还是管理员,因此需要定义一个类去实现这个功能。我们使用UserStorage 类来实现:
package user;
import input.Input;
public class UserStorage {
private final String[] Admin_user = {"lihuanting"};
public boolean isAdmin(String user){
for (String admin: Admin_user) {
if(user.equals(admin)) { // 判断是否相等,使用 equals
return true;
}
}
return false;
}
public User login(Input input){ //传入一个Input对象
String username = input.Prompt("请输入用户名");
if(isAdmin(username)){
return new AdminUser(username);
}
return new CommonUser(username);
}
}
1)每本书(包括每本书的信息等),使用Book类 去实现它。
package book;
public class Book {
public String name;
public String author;
public String type;
public int price;
public String borrowedBy; // 如果没人借走,就是 null
public boolean isBorrowed() {
return borrowedBy != null;
}
public boolean equalsByName(String name) {
// 1. 也是为了封装,看起是一行一个方法
// 2. name 是 String 类型,判断相等性,也需要使用 equals 判断
return this.name.equals(name);
}
}
2)放书的书架(包括其放书,取书功能),使用BookStorage类来实现它。
package book;
import java.util.Arrays;
public class BookStorage {
private Book[] library;
private int size;
public BookStorage(Book[] library, int size) {
size = 0;
library = new Book[20];
}
// 尾插
public void add(Book book) {
// 1. 确认容量够用
ensureCapacity();
library[size++] = book;
}
private void ensureCapacity() {
if (size < library.length) {
// 说明至少还有一个空间,容量够用
return;
}
// 否则进行扩容
library = Arrays.copyOf(library, library.length * 2);
}
public Book[] toArray() {
return Arrays.copyOf(library, size); // 实际上还是有风险的,因为只是浅拷贝
}
// 我们这里隐含一个假设,就是我们的书架中,不允许出现同名的书籍
public Book searchByName(String name) {
// 从 DS 角度,就是顺序表的元素查找问题
for (int i = 0; i < size; i++) {
Book book = library[i];
if (book.equalsByName(name)) {
return book;
}
}
return null;
}
// 原则上,如果顺序表要保持原有顺序的情况下,删除某个元素(可能在中间)
// 则时间复杂度必然是 O(n) 的
// 我们这里需要保持顺序么?array 中的原始顺序其实在当前需求下不是那么重要!
public void remove(Book book) {
for (int i = 0; i < size; i++) {
Book item = library[i];
// Book 正确的重写了 equals 方法
if (item.equals(book)) {
// 把最后一个元素覆盖到此处
library[i] = library[size - 1];
// 把最后一个元素置为 null
library[size - 1] = null;
// 减少 size
size--;
return;
}
}
}
}
(1)可以先实现一个接口类,IExecutable。
package command;
import book.BookStorage;
import input.Input;
import user.User;
public interface IExecutable {
void execute(BookStorage bookStorage, User user, Input input);
String getName();
}
(2)对于命令来说大家可以根据需要来添加,在此我举几个例子来向大家来说明以下。
1.向书架中添加书籍,使用AddBookCommand类来实现。
package command;
import book.Book;
import book.BookStorage;
import input.Input;
import user.User;
public class AddBookCommand implements IExecutable {
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
System.out.println("开始添加书籍,开始读取书籍信息:");
final String name = input.Prompt("请输入书籍名称:");
final String author = input.Prompt("请输入书籍作者:");
final String type = input.Prompt("请输入书籍类型:");
int price;
while (true) {
try {
final String priceStr = input.Prompt("请输入书籍价格(必须是数字)");
price = Integer.parseInt("priceStr");
break;
} catch (NumberFormatException e) {
System.out.println("价格格式错误");
}
}
// 刚加架的书肯定没被借阅
Book book = new Book();
book.name = name;
book.author = author;
book.type = type;
book.price = price;
book.borrowedBy = null;
System.out.println("将书籍添加到 书架上");
bookStorage.add(book);
System.out.println(user.getUsername()+"完成了添加书籍的操作:"+name);
}
@Override
public String getName() {
return "添加书籍";
}
}
2.从书架中删除书籍,使用RemoveBookCommand类来实现。
package command;
import book.Book;
import book.BookStorage;
import input.Input;
import user.User;
public class RemoveBookCommand implements IExecutable {
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
System.out.println("开始进行删除书籍的操作:");
final String name = input.Prompt("请输入要删除的书籍是:");
//增加一些业务逻辑,如果书籍已经借走,则不允许删除
Book book = bookStorage.searchByName(name);
if (book == null) {
System.out.println("没有这本书" + name + "无法删除");
return;
}
if (book.isBorrowed()) {
System.out.println("书籍已经被" + book.borrowedBy + "借走,暂不允许删除");
return;
}
bookStorage.remove(book);
System.out.println(user.getUsername() + "完成了删除书籍的操作" + name);
}
@Override
public String getName() {
return "删除书籍";
}
}
提示:(1)书籍信息需要持久化存储,因此需要想办法把数据放到硬盘上,因此需要将书籍信息储存到文件中,使用只需要操作文件来对书籍信息进行提取。大家可以根据自己的需求来修改文件地址。
(2)我们需要一种序列化格式以便于在文件中读取到书籍信息。例如:每一行存储一本书。书与属性之间用@分割。
修改后的BookStorage类:
package book;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Scanner;
public class BookStorage {
private Book[] library;
private int size;
public BookStorage() {
size = 0;
library = new Book[20];
}
//要让静态方法能使用,也必须要定义静态属性
private static File file = new File("D:\\Desk\\综合练习\\图书管理系统\\src\\file\\bookStorage.txt");
public static BookStorage loadFromFile(){
BookStorage bookStorage = new BookStorage();
if (!file.exists()) {
// 文件不存在,说明是第一次运行,就创建空对象
return bookStorage;
}
// 开始进行加载
try {
Scanner scanner = new Scanner(file, "UTF-8");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
// split 按照指定字符串,进行切割
String[] group = line.split("@");
if (group.length != 5) {
throw new RuntimeException("文件格式不对了");
}
String name = group[0];
String author = group[1];
String type = group[2];
String priceStr = group[3];
int price;
try {
price = Integer.parseInt(priceStr);
} catch (NumberFormatException exc) {
throw new RuntimeException("价格不是数字");
}
String borrowedStr = group[4];
String borrowedBy;
if (borrowedStr.equals("null")) {
borrowedBy = null; // 把字符串 "null" 转成 null
} else {
borrowedBy = borrowedStr;
}
Book book = new Book();
book.name = name;
book.author = author;
book.type = type;
book.price = price;
book.borrowedBy = borrowedBy;
bookStorage.add(book);
}
scanner.close();
return bookStorage;
} catch (IOException exc) {
// 把受查异常转成非受查异常,这样,不会语法抱怨了
throw new RuntimeException(exc);
}
}
// 尾插
public void add(Book book) {
// 1. 确认容量够用
ensureCapacity();
library[size++] = book;
saveToFile();
}
private void ensureCapacity() {
if (size < library.length) {
// 说明至少还有一个空间,容量够用
return;
}
// 否则进行扩容
library = Arrays.copyOf(library, library.length * 2);
}
public Book[] toArray() {
return Arrays.copyOf(library, size); // 实际上还是有风险的,因为只是浅拷贝
}
// 我们这里隐含一个假设,就是我们的书架中,不允许出现同名的书籍
public Book searchByName(String name) {
// 从 DS 角度,就是顺序表的元素查找问题
for (int i = 0; i < size; i++) {
Book book = library[i];
if (book.equalsByName(name)) {
return book;
}
}
return null;
}
// array 中的原始顺序其实在当前需求下不是那么重要!
public void remove(Book book) {
for (int i = 0; i < size; i++) {
Book item = library[i];
// Book 正确的重写了 equals 方法
if (item.equals(book)) {
// 把最后一个元素覆盖到此处
library[i] = library[size - 1];
// 把最后一个元素置为 null
library[size - 1] = null;
// 减少 size
size--;
return;
}
}
saveToFile();
}
// 普通方法,保存是当前 BookStorage 对象的内容
public void saveToFile() {
try {
PrintWriter writer = new PrintWriter(file, "UTF-8");
for (int i = 0; i < size; i++) {
Book book = library[i];
StringBuilder sb = new StringBuilder();
sb.append(book.name);
sb.append("@");
sb.append(book.author);
sb.append("@");
sb.append(book.type);
sb.append("@");
sb.append(book.price);
sb.append("@");
sb.append(book.borrowedBy);
writer.println(sb.toString());
}
writer.flush();
writer.close();
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
}
完整工程文件:链接:https://pan.baidu.com/s/1xU1b8zE8qeK_UA7UunE9Dw?pwd=1234
提取码:1234
git地址:https://github.com/sunnunny22/Library.git