ps:
此工程分为四个包,分别是input(用于输入输出和给出提示信息),command(定义行为),user(普通用户和管理员用户)和main(主函数)
package 图书管理系统;
import 图书管理系统.book.BookStorage;
import 图书管理系统.command.IExecutable;
import 图书管理系统.input.Input;
import 图书管理系统.input.QuitException;
import 图书管理系统.user.User;
import 图书管理系统.user.UserStorage;
public class Main {
// 由于输入过程,需要处理一些杂事
// 1) 打印提示信息
// 2) 读取用户输入
// 3) 用户有没有按 Ctrl + D 退出
// 直接封装一个对象去处理输入的问题
public static void main(String[] args) {
// 0. 实例化一个负责处理输入的对象
Input input = new Input();
// 1. 我们的书架对象,在应用运行过程中,只需要一份
// 2. 改从文件中加载数据
BookStorage bookStorage = BookStorage.loadFromFile();
try {
// 1. 用户登录
// 我们需要一个用户管理的对象,由该对象完成用户登录的具体操作
// 1.1. 要求用户输入
// 1.2. 判断用户角色 ...
UserStorage userStorage = new UserStorage();
User user = userStorage.login(input);
// 2. 进入一个循环中
while (true) {
// 2.1 打印用户角色对应的菜单,并且让用户选择
// execute : 执行
// executable : 具备可以执行的能力
// IExecutable : 计划用接口去表示
// command: 命令
IExecutable command = input.menuAndSelect(user);
// 2.2 根据用户的选择,执行对应的操作命令
command.execute(bookStorage, user, input);
}
} catch (QuitException exc) {
// 用户要退出,我们什么都不做
}
// 3. 用户退出,打印退出信息
System.out.println("欢迎下次使用");
}
}
book包:
package 图书管理系统.book;
import 图书管理系统.user.User;
public class Book implements Comparable {
public String name;
public String author;
public String type;
public int price;
public String borrowedBy;
@Override
public int compareTo(Object o) {
Book b=(Book) o;
return name.compareTo(b.name);// 自然顺序是以书名为顺序
// 书名也是 String 类型,也得遵守对象的比较规则
}
@Override
public String toString() {
//强行用StringBuilder
String.format("《%s》by %s,[%s],%d",name,author,type,price);
StringBuilder sb=new StringBuilder();
if (borrowedBy==null){
sb.append("可借用!");
}else {
sb.append("被");
sb.append(borrowedBy);
sb.append("借走了");
}
return sb.toString();
}
public boolean isBorrowed(){
return borrowedBy==null;
}
public boolean equalsByName(String name){
return this.name.equals(name);// 1. 也是为了封装,看起是一行一个方法
// 2. name 是 String 类型,判断相等性,也需要使用 equals 判断
}
public void borrowed(User user){
borrowedBy=user.getUsername();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return name.equals(book.name);
}
public boolean isBorrowedBy(User user){
if (borrowedBy==null){
return false;
}
return borrowedBy.equals(user.getUsername());
}
public void returned(User user){//换书操作
borrowedBy=null;
}
}
package 图书管理系统.book;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Scanner;
public class BookStorage {
private Book[] array;
private int size;
public BookStorage(){
array=new Book[20];
size=0;
}
// 要让静态方法能使用,也得使用静态属性
private static final File file=new File("C:\\java data\\.metadata\\book-storage.txt");
//直接使用类名调用的一定是静态方法
public static BookStorage loadFromFile(){
BookStorage bookStorage=new BookStorage();
if (!file.exists()){
return bookStorage; //说明第一次运行,创建一个空对象
}
//开始加载
try {
Scanner sc=new Scanner(file,"UTF-8");
while (sc.hasNextLine()){
String line=sc.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 borrowedByStr=group[4];
String borrowedBy;
if (borrowedByStr.equals("null")){
borrowedBy=null;// 把字符串 "null" 转成 null
}else {
borrowedBy=borrowedByStr;
}
Book book=new Book();
book.name=name;
book.author = author;
book.type = type;
book.price = price;
book.borrowedBy = borrowedBy;
bookStorage.add(book);
}
sc.close();
return bookStorage;
}catch (IOException e){
throw new RuntimeException(e);
}
}
public void add(Book book) {
//尾插
ensureCapacity();
array[size++]=book;
saveToFile();
}
public Book[] toArray(){
//return array; // 直接返回可不可以? 语法上成立。不可以
// 理论有 两条:
// 1. 返回 array,它的长度是 array.length ,但我们真正的书籍只有 size 本
// 应该是返回一个 长度是 size 的数组,而不是长度是 array.length 的数组
// 2. 一旦把 array 返回出去之后(数组对象逃逸了)
// 别人会怎么操作数组对象,我们完全无法控制
// 所以可能打破顺序表的一致性问题。比如 Arrays.fill(array, null)
// size > 0 但是 array 中全部是 null
return Arrays.copyOf(array,size);
// 实际上还是有风险的,因为只是浅拷贝
// 如果使用者有意还是无意改了 Book 对象属性
// 我们还是会收到影响的
// 最好的办法是做深拷贝,但这里不做那么复杂了
}
private void ensureCapacity() {
if (size<array.length){
return;
}
array= Arrays.copyOf(array,array.length*2);
}
// TODO: 我们这里隐含一个假设,就是我们的书架中,不允许出现同名的书籍
// TODO: 否则删除的时候操作有歧义
// TODO: 这个判断,我们在 AddBookCommand 中是没有操作
public Book searchByName(String name){
for (int i=0;i<size;i++){
Book book=array[i];
if (book.equals(name)){
return book;
}
}
return null;
}
// 原则上,如果顺序表要保持原有顺序的情况下,删除某个元素(可能在中间)
// 则时间复杂度必然是 O(n) 的
// 我们这里需要保持顺序么?array 中的原始顺序其实在当前需求下不是那么重要!
public void remove(Book name){
for (int i=0;i<size;i++){
Book item=new Book();
if (item.equals(name)){
array[i]=array[size-1];
array[size-1]=null;
size--;
return;
}
}
saveToFile();
}
// 普通方法,保存是当前 BookStorage 对象的内容
public void saveToFile() {
try{
PrintWriter writer=new PrintWriter(file,"UTF-8");
for (int i=0;i<size;i++){
Book book=array[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);
}
}
}
input包:
package 图书管理系统.input;
import 图书管理系统.command.IExecutable;
import 图书管理系统.user.User;
import java.util.Scanner;
public class Input {
Scanner scanner=new Scanner(System.in);
public String prompt(String prompt){
System.out.println(prompt+":");
System.out.print(">");
if (!scanner.hasNextLine()){//说明用户按下了ctrl+D,想要退出
throw new QuitException();//通过异常向外部通知
}
return scanner.nextLine();
}
public IExecutable menuAndSelect(User user){
IExecutable[] supportedCommands = user.getSupportedCommands();
while (true){
showMenu(supportedCommands);
// 角色不同,理论上拥有的功能不同
// 需要当前用户信息
String selectStr=prompt("请选择您要进行的操作");
try {
int select=Integer.parseInt(selectStr);
if (select>=1&select<=supportedCommands.length){
return supportedCommands[select-1];
}
System.out.println("请输入正确的序号!");
}catch (NumberFormatException exc){
System.out.println("请输入正确的数字");
}
}
}
private void showMenu(IExecutable[] supportedCommands) {
// 2. 遍历并打印每个命令的名称,显示操作菜单
System.out.println("----------------");
for (int i=0;i<supportedCommands.length;i++){
IExecutable command=supportedCommands[i];
System.out.printf(" %2d. %s\n", i + 1, command.getName());
}
System.out.println("----------------");
}
}
package 图书管理系统.input;
public class QuitException extends RuntimeException{
}
user包:
package 图书管理系统.user;
import 图书管理系统.command.IExecutable;
public abstract class User {
private String username;
public User(String name){
this.username=name;
}
public String getUsername(){
return username;
}
// 所以也完全不知道支持哪些命令
// 应该定义成抽象方法,供子类去实现
public abstract IExecutable[] getSupportedCommands();
}
package 图书管理系统.user;
import 图书管理系统.command.*;
public class AdminUser extends User {
public AdminUser(String name) {
super(name);
}
@Override
public IExecutable[] getSupportedCommands() {
return new IExecutable[] {
// 管理员支持 添加书籍命令
new AddBookCommand(),
// 管理员支持 删除书籍命令
new RemoveBookCommand(),
// 管理员支持 根据书名列出书籍命令
new ListBookOrderByNameCommand(),
// 管理员支持 根据价格列出书籍命令
new ListBookOrderByPriceCommand(),
// 管理员支持 根据借阅情况列出书籍命令
new ListBookOrderByBorrowedCommand()
};
}
}
package 图书管理系统.user;
import 图书管理系统.command.*;
public class CommandUser extends User {
public CommandUser(String name) {
super(name);
}
@Override
public IExecutable[] getSupportedCommands() {
return new IExecutable[] {
new ListBookOrderByNameCommand(),
new ListBookOrderByPriceCommand(),
new ListBookOrderByBorrowedCommand(),
new BorrowBookCommand(),
new ReturnBookCommand()
};
}
}
package 图书管理系统.user;
import 图书管理系统.input.Input;
public class UserStorage {
// 提前定义一些用户名,作为管理员
private static final String[] ADMIN_USERNAMES={
"文佳怡",
"耿超扬"
};
private boolean isAdmin(String name){
for (String admin:ADMIN_USERNAMES){
if (admin.equals(name)){
return true;
}
}
return false;
}
public User login(Input input){
// 1. 先让用户输入用户名
//prompt:提示符
String username=input.prompt("请输入用户名");
// 2. 根据用户名,角色是管理员还是普通的用户
// 3. 根据不同的角色,创建不同的用户
// admin: 管理员
if (isAdmin(username)){
return new AdminUser(username);
}else {
return new CommandUser(username);
}
}
}
comand:
package 图书管理系统.command;
import 图书管理系统.book.Book;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Comparator;
// 由于 AbsListBookCommand 是 抽象类,允许出现抽象方法
public abstract class AbsListBookCommand implements IExecutable {
protected abstract Comparator getComparator();
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
Comparator comparator=getComparator();
Book[] books=bookStorage.toArray();
if (comparator==null){
Arrays.sort(books);
}else {
Arrays.sort(books,comparator);
}
for (Book book:books){
System.out.println(book);
}
}
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
public class AddBookCommand implements IExecutable {
@Override
public String getName() {
return "添加书籍";
}
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
System.out.println("开始添加书籍,首先读取书籍信息:");
String name = input.prompt("请输入书籍名称");
String author = input.prompt("请输入书籍作者");
String type = input.prompt("请输入书籍类型");
int price;
while (true) {
try {
String priceStr = input.prompt("请输入书籍价格(必须是数字)");
price = Integer.parseInt(priceStr);
break;
} catch (NumberFormatException exc) {
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);
}
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
public class BorrowBookCommand implements IExecutable {
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
// 通过书名,完成借阅
System.out.println("开始借阅书籍,首先读取书籍信息:");
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;
}
// 由于 book 返回的引用,指向的书籍对象,和 BookStorage 中保存的 Book 对象是同一个对象
// 通过 book 引用修改,也会让 BookStorage 中生效
book.borrowed(user);
bookStorage.saveToFile();
System.out.println(user.getUsername() + " 完成了 借阅书籍的操作: " + name);
}
@Override
public String getName() {
return "借阅书籍";
}
}
package 图书管理系统.command;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
public interface IExecutable {
void execute(BookStorage bookStorage, User user, Input input);
String getName();
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import java.util.Comparator;
public class ListBookOrderByBorrowedCommand extends AbsListBookCommand {
private final Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Book b1 = (Book) o1;
Book b2 = (Book) o2;
int br1 = b1.borrowedBy == null ? 0 : 1;
int br2 = b2.borrowedBy == null ? 0 : 1;
return br1 - br2; // 未借走的书籍比较“小”
}
};
@Override
public String getName() {
return "根据借阅情况排序,列出书籍";
}
@Override
protected Comparator getComparator() {
return comparator;
}
}
package 图书管理系统.command;
import java.util.Comparator;
public class ListBookOrderByNameCommand extends AbsListBookCommand {
@Override
public String getName() {
return "根据书名排序,列出书籍";
}
@Override
protected Comparator getComparator() {
return null;
}
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import java.util.Comparator;
public class ListBookOrderByPriceCommand extends AbsListBookCommand {
private static class PriceComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Book b1 = (Book) o1;
Book b2 = (Book) o2;
return b1.price - b2.price;
}
}
private final Comparator comparator = new PriceComparator();
@Override
protected Comparator getComparator() {
return comparator;
}
@Override
public String getName() {
return "根据价格排序,列出书籍";
}
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
public class RemoveBookCommand implements IExecutable {
@Override
public String getName() {
return "删除书籍";
}
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
// 只支持按照名称删除
System.out.println("开始删除书籍,首先读取要删除的书籍信息:");
String name = input.prompt("请输入书籍名称");
// 增加一些业务逻辑:如果书籍已经借走,则不允许删除
Book book = bookStorage.searchByName(name);
if (book == null) {
// 没有找到
System.out.println("没有这本书 " + name + ",无法删除");
return;
}
// 看起来 book.isBorrowed() 和 book.borrowedBy != null 是逻辑等价的
// 应该用 book.isBorrowed() 比 后边更好,为什么?
// 从职责划分的角度(封装)
// 提到了方法的重用挺好,但这里的原因不是这个
if (book.isBorrowed()) {
System.out.println("书籍已经被 " + book.borrowedBy + " 借走,暂时不允许删除");
return;
}
bookStorage.remove(book);
System.out.println(user.getUsername() + " 完成了 删除书籍的操作: " + name);
}
}
package 图书管理系统.command;
import 图书管理系统.book.Book;
import 图书管理系统.book.BookStorage;
import 图书管理系统.input.Input;
import 图书管理系统.user.User;
public class ReturnBookCommand implements IExecutable {
@Override
public void execute(BookStorage bookStorage, User user, Input input) {
// 通过书名,完成归还
System.out.println("开始借阅书籍,首先读取书籍信息:");
String name = input.prompt("请输入书籍名称");
Book book = bookStorage.searchByName(name);
if (book == null) {
// 没有找到
System.out.println("没有这本书 " + name + ",无法归还");
return;
}
if (!book.isBorrowed()) {
System.out.println("书籍没有被借走,不需要归还");
return;
}
if (!book.isBorrowedBy(user)) {
System.out.println("书籍不是被 " + user.getUsername() + " 借走的,无法归还");
return;
}
book.returned(user);
bookStorage.saveToFile();
System.out.println(user.getUsername() + " 完成了 归还书籍的操作: " + name);
}
@Override
public String getName() {
return "归还书籍";
}
}