用 Java 模拟一个图书馆。包括创建图书、创建读者、借书、还书、列出所有图书、列出所有读者、列出已借出的图书、列出过期未还的图书等功能。每个读者最多只能借 3 本书,每个书最多只能借 3 个星期,超过就算过期。
下面是一个命令行下的实现。这个例子的主要目的是向初学者展示内部类的好处。Command 及其子类都是 LibrarySimulator 的内部类。它们可以无阻碍的访问 LibrarySimulator 的成员。使用内部类,而不是大量的 if-else,让程序更容易扩展。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 一个图书馆的课程设计。主要功能:
* 1. 创建图书
* 2. 创建读者
* 3. 借书
* 4. 还书
* 5. 列出所有书
* 6. 列出已借书
* 7. 列出超过日期未还的书
*/
public class LibrarySimulator {
// 主菜单
private static final String MAIN_MENU = "1. 列出所有的书\n" +
"2. 列出已借出的书\n" +
"3. 列出过期未还的书\n" +
"4. 列出所有读者\n" +
"5. 创建图书\n" +
"6. 创建读者\n" +
"7. 借书\n" +
"8. 还书\n" +
"9. 退出\n" +
"请输入序号:";
// 选择图书类型的菜单。在借书和添加图书的时候都会用到
private static final String TYPE_MENU;
// 表示一个数字的正则表达式
private static final String DIGIT_CHOICE_PATTERN = "^\\d$";
// 表示非空字符串
private static final String NOT_EMPTY_PATTERN = "\\S.*";
// 日期格式
static final String DATE_PATTERN = "yyyy/MM/dd";
// 验证用户输入日期的正则表达式
static final String DATE_FORMAT_PATTERN = "^\\d{4}/\\d{2}/\\d{2}$";
// 预定义的图书类型
static HashMap<String, String> TYPES = new LinkedHashMap<String, String>();
static {
TYPES.put("1", "科学类");
TYPES.put("2", "文学类"); // 新的类别可以继续在后面添加
TYPE_MENU = createTypeMenu();
}
// 生成选择类别的菜单
private static String createTypeMenu() {
String str = "";
for (String index : TYPES.keySet()) {
str += index + ". " + TYPES.get(index) + "\n";
}
return str + "请选择书的类型:";
}
private HashMap<Integer, Command> commands = new HashMap<Integer, Command>();
private ArrayList<Book> books = new ArrayList<Book>();
private ArrayList<Reader> readers = new ArrayList<Reader>();
// 程序入口。这里创建一个 LibrarySimulator 用于模拟界面。
public static void main(String[] args) {
new LibrarySimulator().start();
}
/**
* 构造函数
*/
public LibrarySimulator() {
commands.put(1, new Command1());
commands.put(2, new Command2());
commands.put(3, new Command3());
commands.put(4, new Command4());
commands.put(5, new Command5());
commands.put(6, new Command6());
commands.put(7, new Command7());
commands.put(8, new Command8());
}
/**
* 这里接受用户输入,执行操作,然后再等待用户输入,这样不停的循环。
*/
private void start() {
String index = prompt(MAIN_MENU, DIGIT_CHOICE_PATTERN);
while (!index.equals("9")) {
executeCommand(index);
index = prompt(MAIN_MENU, DIGIT_CHOICE_PATTERN);
}
}
// 根据序号执行命令
private void executeCommand(String index) {
Command command = commands.get(Integer.parseInt(index));
if (command != null) {
String result = command.execute();
System.out.println(result + "\n");
}
}
// 打印一条提示信息,然后读取并返回用户输入
private String prompt(String message, String pattern) {
System.out.print(message);
if (pattern == null) {
return readInput();
} else {
String result = "";
while (!result.matches(pattern)) {
result = readInput();
}
return result;
}
}
// 读取用户输入
private String readInput() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
return reader.readLine();
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
// 根据名字查找读者。找不到则返回 null。
private Reader getReaderByName(String readerName) {
for (Reader reader : readers) {
if (reader.getName().equals(readerName)) {
return reader;
}
}
return null;
}
// 根据名字查找图书。找不到则返回 null。
private Book getBookByName(String bookName) {
for (Book book : books) {
if (book.getName().equals(bookName)) {
return book;
}
}
return null;
}
/*===================================================================*/
/**
* 代表命令的抽象类
*/
private abstract class Command {
protected abstract String execute();
}
/// 列出所有图书
private class Command1 extends Command {
protected String execute() {
for (Book book : getBooks()) {
System.out.println(book); // 这里会自动调用 book.toString()
}
return "命令完成。";
}
private ArrayList<Book> getBooks() {
ArrayList<Book> result = new ArrayList<Book>();
for (Book book : books) {
if (isValid(book)) {
result.add(book);
}
}
return result;
}
// 考虑到第 1、2、3 条命令大体相同,这里提供了一个给子类覆写的方法
protected boolean isValid(Book book) {
return true;
}
}
/ 列出已借出的书。
// 注意它的父类不是 Command,而是 Command1。这样节省了很多重复代码
private class Command2 extends Command1 {
@Override
protected boolean isValid(Book book) {
return book.isBorrowed();
}
}
列出过期未还的书
private class Command3 extends Command1 {
@Override
protected boolean isValid(Book book) {
// 判断一本书接触过期与否的方法最好在 Book 类中去实现。
return book.isExpired();
}
}
/// 创建图书
private class Command5 extends Command {
protected String execute() {
String type = getType();
String name = getName();
if (getBookByName(name) == null) {
books.add(new Book(type, name));
return "图书添加成功。";
} else {
return "图书添加失败:名称已存在。";
}
}
// 获得用户输入的书名
private String getName() {
return prompt("请输入书名:", NOT_EMPTY_PATTERN);
}
// 获得用户选择的图书类型
private String getType() {
return prompt(TYPE_MENU, DIGIT_CHOICE_PATTERN);
}
}
/// 列出所有读者
private class Command4 extends Command {
protected String execute() {
for (Reader reader : readers) {
System.out.println(reader);
}
return "命令完成。";
}
}
/// 创建读者
private class Command6 extends Command {
protected String execute() {
String name = getName();
if (getReaderByName(name) == null) {
readers.add(new Reader(name));
return "读者创建成功。";
} else {
return "读者创建失败:名字已经存在。";
}
}
public String getName() {
return prompt("请输入读者名字:", NOT_EMPTY_PATTERN);
}
}
/// 借书
private class Command7 extends Command {
protected String execute() {
Reader reader = getReader();
if (reader == null) {
System.out.println("命令取消。");
return "";
}
Book book = getBook();
if (book == null) {
System.out.println("命令取消。");
return "";
}
String borrowDate = getBorrowDate();
book.borrowBy(reader.getName(), borrowDate);
reader.addBorrowCount();
return "成功借出。";
}
private String getBorrowDate() {
String now = new SimpleDateFormat(LibrarySimulator.DATE_PATTERN).format(new Date());
String date = null;
while (date == null || !date.matches(DATE_FORMAT_PATTERN)) {
date = prompt("请输入借出日期(如" + now + ")", NOT_EMPTY_PATTERN);
}
return date;
}
private Book getBook() {
Book book = null;
while (book == null || book.isBorrowed()) {
String bookName = prompt("请输入图书名字:", null);
if (bookName.equals("")) {
return null;
}
book = getBookByName(bookName);
if (book == null) {
System.out.println("图书不存在。");
} else if (book.isBorrowed()) {
System.out.println("图书已经被借出。");
}
}
return book;
}
private Reader getReader() {
Reader reader = null;
while (reader == null || !reader.canBorrow()) {
String readerName = prompt("请输入读者名字:", null);
if (readerName.equals("")) {
return null;
}
reader = getReaderByName(readerName);
if (reader == null) {
System.out.println("读者不存在。");
} else if (!reader.canBorrow()) {
System.out.println("该读者已经借了" + Reader.MAX_BORROW + " 本书,不能继续借了。");
}
}
return reader;
}
}
/ 还书
private class Command8 extends Command {
protected String execute() {
Reader reader = getReader();
if (reader == null) {
System.out.println("命令取消。");
return "";
}
Book book = getBook(reader);
if (book == null) {
System.out.println("命令取消。");
return "";
}
reader.reduceBorrowCount();
book.returned();
return "操作成功。";
}
private Book getBook(Reader reader) {
Book book = null;
while (book == null || !reader.getName().equals(book.getBorrower())) {
String bookName = prompt("请输入图书名字:", null);
if (bookName.equals("")) {
return null;
}
book = getBookByName(bookName);
if (book == null) {
System.out.println("图书不存在。");
} else if (!reader.getName().equals(book.getBorrower())) {
System.out.println("该读者没有借出这本书。");
}
}
return book;
}
private Reader getReader() {
Reader reader = null;
while (reader == null) {
String readerName = prompt("请输入读者名字:", null);
if (readerName.equals("")) {
return null;
}
reader = getReaderByName(readerName);
if (reader == null) {
System.out.println("读者不存在。");
}
}
return reader;
}
}
}
// 图书
class Book {
public static final int EXPIRE_DAYS = 21; // 可借出天数,超过就算过期
private String type;
private String name;
private String borrowedBy = null;
private String borrowDate = null;
Book(String type, String name) {
this.type = type;
this.name = name;
}
@Override
public String toString() {
String str = String.format("类别:%s 书名:%s", LibrarySimulator.TYPES.get(type), name);
if (isBorrowed()) {
str += " 借出人:" + borrowedBy + " 借出时间:" + borrowDate;
}
return str;
}
public boolean isBorrowed() {
return borrowedBy != null;
}
public String getName() {
return name;
}
public String getBorrowDate() {
return borrowDate;
}
/**
* 图书借出
*
* @param name 读者名字
* @param date 借出日期。格式:参见 {@link LibrarySimulator#DATE_PATTERN}
*/
public void borrowBy(String name, String date) {
this.borrowedBy = name;
this.borrowDate = date;
}
public boolean isExpired() {
if (borrowDate == null) {
return false; // 没有借出的书不出现在过期未还列表当中,所以这里返回 false。
}
// 从当前时间往前推 3 个星期,如果还在借书日期之后,说明借书已经超过 3 个星期了
String threeWksAgo = get3WeeksAgo();
return threeWksAgo.compareTo(borrowDate) > 0;
}
// 获得 3 个星期前的日期
private String get3WeeksAgo() {
SimpleDateFormat f = new SimpleDateFormat(LibrarySimulator.DATE_PATTERN);
Calendar c = Calendar.getInstance();
c.add(Calendar.DAY_OF_MONTH, -EXPIRE_DAYS);
return f.format(c.getTime());
}
public void returned() {
this.borrowBy(null, null);
}
public String getBorrower() {
return borrowedBy;
}
}
// 读者
class Reader {
// 每位读者最多可同时借出 3 本书
public static final int MAX_BORROW = 3;
private String name;
private int borowCount = 0;
public int getBorowCount() {
return borowCount;
}
Reader(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addBorrowCount() {
borowCount++;
}
public void reduceBorrowCount() {
borowCount--;
}
public boolean canBorrow() {
return borowCount < MAX_BORROW;
}
@Override
public String toString() {
return name;
}
}
PS: 这种小东西竟然被丢到首页上去了,绝不是我的意思。太丢人了。