用 Java 模擬一個圖書館。包括創建圖書、創建讀者、借書、還書、列出所有圖書、 列出所有讀者、列出已借出的圖書、列出過期未還的圖書等功能。每個讀者最多只能借 3 本書,每個書最多只能借 3 個星期,超過就算過期。
下面是一個命令行下的實現。這個例子的主要目的是向初學者展示內部類的好處。 Command 及其子類都是 LibrarySimulator 的內部類。它們可以無阻礙的訪問 LibrarySimulator 的成員。使用內部類,而不是大量的 if-else,讓程序更容易擴展。
01.import java.io.BufferedReader;
02.import java.io.IOException;
03.import java.io.InputStreamReader;
04.import java.text.SimpleDateFormat;
05.import java.util.*;
06.
07./**
08. * 一個圖書館的課程設計。主要功能:
09. * 1. 創建圖書
10. * 2. 創建讀者
11. * 3. 借書
12. * 4. 還書
13. * 5. 列出所有書
14. * 6. 列出已借書
15. * 7. 列出超過日期未還的書
16. */
17.public class LibrarySimulator {
18.
19. // 主菜單
20. private static final String MAIN_MENU = "1. 列出所有的書\n" +
21. "2. 列出已借出的書\n" +
22. "3. 列出過期未還的書\n" +
23. "4. 列出所有讀者\n" +
24. "5. 創建圖書\n" +
25. "6. 創建讀者\n" +
26. "7. 借書\n" +
27. "8. 還書\n" +
28. "9. 退出\n" +
29. "請輸入序號:";
30.
31. // 選擇圖書類型的菜單。在借書和添加圖書的時候都會用到
32. private static final String TYPE_MENU;
33.
34. // 表示一個數字的正則表達式
35. private static final String DIGIT_CHOICE_PATTERN = "^\\d$";
36.
37. // 表示非空字符串
38. private static final String NOT_EMPTY_PATTERN = "\\S.*";
39.
40. // 日期格式
41. static final String DATE_PATTERN = "yyyy/MM/dd";
42.
43. // 驗證用戶輸入日期的正則表達式
44. static final String DATE_FORMAT_PATTERN = "^\\d{4}/\\d{2}/\\d{2}$";
45.
46. // 預定義的圖書類型
47. static HashMap<String, String> TYPES = new LinkedHashMap<String, String>();
48.
49. static {
50. TYPES.put("1", "科學類");
51. TYPES.put("2", "文學類"); // 新的類別可以繼續在後面添加
52. TYPE_MENU = createTypeMenu();
53. }
54.
55. // 生成選擇類別的菜單
56. private static String createTypeMenu() {
57. String str = "";
58. for (String index : TYPES.keySet()) {
59. str += index + ". " + TYPES.get(index) + "\n";
60. }
61. return str + "請選擇書的類型:";
62. }
63.
64.
65. private HashMap<Integer, Command> commands = new HashMap<Integer, Command>();
66.
67. private ArrayList<Book> books = new ArrayList<Book>();
68.
69. private ArrayList<Reader> readers = new ArrayList<Reader>();
70.
71. // 程序入口。這裡創建一個 LibrarySimulator 用於模擬界面。
72. public static void main(String[] args) {
73. new LibrarySimulator().start();
74. }
75.
76. /**
77. * 構造函數
78. */
79. public LibrarySimulator() {
80. commands.put(1, new Command1());
81. commands.put(2, new Command2());
82. commands.put(3, new Command3());
83. commands.put(4, new Command4());
84. commands.put(5, new Command5());
85. commands.put(6, new Command6());
86. commands.put(7, new Command7());
87. commands.put(8, new Command8());
88. }
89.
90. /**
91. * 這裡接受用戶輸入,執行操作,然後再等待用戶輸入,這樣不停的循環。
92. */
93. private void start() {
94. String index = prompt(MAIN_MENU, DIGIT_CHOICE_PATTERN);
95.
96. while (!index.equals("9")) {
97. executeCommand(index);
98. index = prompt(MAIN_MENU, DIGIT_CHOICE_PATTERN);
99. }
100. }
101.
102. // 根據序號執行命令
103. private void executeCommand(String index) {
104. Command command = commands.get(Integer.parseInt(index));
105. if (command != null) {
106. String result = command.execute();
107. System.out.println(result + "\n");
108. }
109. }
110.
111. // 打印一條提示信息,然後讀取並返回用戶輸入
112. private String prompt(String message, String pattern) {
113. System.out.print(message);
114. if (pattern == null) {
115. return readInput();
116. } else {
117. String result = "";
118. while (!result.matches(pattern)) {
119. result = readInput();
120. }
121. return result;
122. }
123. }
124.
125. // 讀取用戶輸入
126. private String readInput() {
127. try {
128. BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
129. return reader.readLine();
130. } catch (IOException e) {
131. e.printStackTrace();
132. return "";
133. }
134. }
135.
136. // 根據名字查找讀者。找不到則返回 null。
137. private Reader getReaderByName(String readerName) {
138. for (Reader reader : readers) {
139. if (reader.getName().equals(readerName)) {
140. return reader;
141. }
142. }
143. return null;
144. }
145.
146. // 根據名字查找圖書。找不到則返回 null。
147. private Book getBookByName(String bookName) {
148. for (Book book : books) {
149. if (book.getName().equals(bookName)) {
150. return book;
151. }
152. }
153. return null;
154. }
155.
156. /*===================================================================*/
157.
158. /**
159. * 代表命令的抽象類
160. */
161. private abstract class Command {
162.
163. protected abstract String execute();
164. }
165.
166. /////////////////////////////////////////////////// 列出所有圖書
167. private class Command1 extends Command {
168.
169. protected String execute() {
170. for (Book book : getBooks()) {
171. System.out.println(book); // 這裡會自動調用 book.toString()
172. }
173. return "命令完成。";
174. }
175.
176. private ArrayList<Book> getBooks() {
177. ArrayList<Book> result = new ArrayList<Book>();
178.
179. for (Book book : books) {
180. if (isValid(book)) {
181. result.add(book);
182. }
183. }
184. return result;
185. }
186.
187. // 考慮到第 1、2、3 條命令大體相同,這裡提供了一個給子類覆寫的方法
188. protected boolean isValid(Book book) {
189. return true;
190. }
191. }
192.
193. ///////////////////////////////////////////////////// 列出已借出的書。
194. // 注意它的父類不是 Command,而是 Command1。這樣節省了很多重復代碼
195. private class Command2 extends Command1 {
196.
197. @Override
198. protected boolean isValid(Book book) {
199. return book.isBorrowed();
200. }
201. }
202.
203. //////////////////////////////////////////////////////// 列出過期未還的書
204. private class Command3 extends Command1 {
205.
206. @Override
207. protected boolean isValid(Book book) {
208. // 判斷一本書接觸過期與否的方法最好在 Book 類中去實現。
209. return book.isExpired();
210. }
211. }
212.
213. /////////////////////////////////////////////// 創建圖書
214. private class Command5 extends Command {
215.
216. protected String execute() {
217. String type = getType();
218. String name = getName();
219. if (getBookByName(name) == null) {
220. books.add(new Book(type, name));
221. return "圖書添加成功。";
222. } else {
223. return "圖書添加失敗:名稱已存在。";
224. }
225. }
226.
227. // 獲得用戶輸入的書名
228. private String getName() {
229. return prompt("請輸入書名:", NOT_EMPTY_PATTERN);
230. }
231.
232. // 獲得用戶選擇的圖書類型
233. private String getType() {
234. return prompt(TYPE_MENU, DIGIT_CHOICE_PATTERN);
235. }
236. }
237.
238. /////////////////////////////////////////////////////// 列出所有讀者
239. private class Command4 extends Command {
240.
241. protected String execute() {
242. for (Reader reader : readers) {
243. System.out.println(reader);
244. }
245. return "命令完成。";
246. }
247. }
248.
249. /////////////////////////////////////////////////////// 創建讀者
250. private class Command6 extends Command {
251.
252. protected String execute() {
253. String name = getName();
254. if (getReaderByName(name) == null) {
255. readers.add(new Reader(name));
256. return "讀者創建成功。";
257. } else {
258. return "讀者創建失敗:名字已經存在。";
259. }
260. }
261.
262. public String getName() {
263. return prompt("請輸入讀者名字:", NOT_EMPTY_PATTERN);
264. }
265. }
266.
267. /////////////////////////////////////////////////////// 借書
268. private class Command7 extends Command {
269.
270. protected String execute() {
271. Reader reader = getReader();
272. if (reader == null) {
273. System.out.println("命令取消。");
274. return "";
275. }
276.
277. Book book = getBook();
278. if (book == null) {
279. System.out.println("命令取消。");
280. return "";
281. }
282.
283. String borrowDate = getBorrowDate();
284.
285. book.borrowBy(reader.getName(), borrowDate);
286. reader.addBorrowCount();
287.
288. return "成功借出。";
289. }
290.
291. private String getBorrowDate() {
292. String now = new SimpleDateFormat(LibrarySimulator.DATE_PATTERN).format(new Date());
293. String date = null;
294. while (date == null || !date.matches(DATE_FORMAT_PATTERN)) {
295. date = prompt("請輸入結束日期(如" + now + ")", NOT_EMPTY_PATTERN);
296. }
297. return date;
298. }
299.
300. private Book getBook() {
301. Book book = null;
302. while (book == null || book.isBorrowed()) {
303. String bookName = prompt("請輸入圖書名字:", null);
304. if (bookName.equals("")) {
305. return null;
306. }
307.
308. book = getBookByName(bookName);
309. if (book == null) {
310. System.out.println("圖書不存在。");
311. } else if (book.isBorrowed()) {
312. System.out.println("圖書已經被借出。");
313. }
314. }
315. return book;
316. }
317.
318. private Reader getReader() {
319. Reader reader = null;
320. while (reader == null || !reader.canBorrow()) {
321. String readerName = prompt("請輸入讀者名字:", null);
322. if (readerName.equals("")) {
323. return null;
324. }
325.
326. reader = getReaderByName(readerName);
327. if (reader == null) {
328. System.out.println("讀者不存在。");
329. } else if (!reader.canBorrow()) {
330. System.out.println("該讀者已經借了" + Reader.MAX_BORROW + " 本書,不能繼續借了。");
331. }
332. }
333. return reader;
334. }
335. }
336.
337. ///////////////////////////////////////////// 還書
338. private class Command8 extends Command {
339.
340. protected String execute() {
341. Reader reader = getReader();
342. if (reader == null) {
343. System.out.println("命令取消。");
344. return "";
345. }
346.
347. Book book = getBook(reader);
348. if (book == null) {
349. System.out.println("命令取消。");
350. return "";
351. }
352.
353. reader.reduceBorrowCount();
354. book.returned();
355. return "操作成功。";
356. }
357.
358. private Book getBook(Reader reader) {
359. Book book = null;
360. while (book == null || !reader.getName().equals(book.getBorrower())) {
361. String bookName = prompt("請輸入圖書名字:", null);
362. if (bookName.equals("")) {
363. return null;
364. }
365.
366. book = getBookByName(bookName);
367. if (book == null) {
368. System.out.println("圖書不存在。");
369. } else if (!reader.getName().equals(book.getBorrower())) {
370. System.out.println("該讀者沒有借出這本書。");
371. }
372. }
373. return book;
374. }
375.
376. private Reader getReader() {
377. Reader reader = null;
378. while (reader == null) {
379. String readerName = prompt("請輸入讀者名字:", null);
380. if (readerName.equals("")) {
381. return null;
382. }
383.
384. reader = getReaderByName(readerName);
385. if (reader == null) {
386. System.out.println("讀者不存在。");
387. }
388. }
389. return reader;
390. }
391. }
392.}
393.
394.// 圖書
395.class Book {
396.
397. public static final int EXPIRE_DAYS = 21; // 可借出天數,超過就算過期
398.
399. private String type;
400.
401. private String name;
402.
403. private String borrowedBy = null;
404.
405. private String borrowDate = null;
406.
407. Book(String type, String name) {
408. this.type = type;
409. this.name = name;
410. }
411.
412. @Override
413. public String toString() {
414. String str = String.format("類別:%s 書名:%s", LibrarySimulator.TYPES.get(type), name);
415. if (isBorrowed()) {
416. str += " 借出人:" + borrowedBy + " 借出時間:" + borrowDate;
417. }
418. return str;
419. }
420.
421. public boolean isBorrowed() {
422. return borrowedBy != null;
423. }
424.
425. public String getName() {
426. return name;
427. }
428.
429. public String getBorrowDate() {
430. return borrowDate;
431. }
432.
433. /**
434. * 圖書借出
435. *
436. * @param name 讀者名字
437. * @param date 借出日期。格式:參見 {@link LibrarySimulator#DATE_PATTERN}
438. */
439. public void borrowBy(String name, String date) {
440. this.borrowedBy = name;
441. this.borrowDate = date;
442. }
443.
444. public boolean isExpired() {
445. if (borrowDate == null) {
446. return false; // 沒有借出的書不出現在過期未還列表當中,所以這裡返回 false。
447. }
448.
449. // 從當前時間往前推 3 個星期,如果還在借書日期之後,說明借書已經超過 3 個星期了
450. String threeWksAgo = get3WeeksAgo();
451. return threeWksAgo.compareTo(borrowDate) > 0;
452. }
453.
454. // 獲得 3 個星期前的日期
455. private String get3WeeksAgo() {
456. SimpleDateFormat f = new SimpleDateFormat(LibrarySimulator.DATE_PATTERN);
457. Calendar c = Calendar.getInstance();
458. c.add(Calendar.DAY_OF_MONTH, -EXPIRE_DAYS);
459. return f.format(c.getTime());
460. }
461.
462. public void returned() {
463. this.borrowBy(null, null);
464. }
465.
466. public String getBorrower() {
467. return borrowedBy;
468. }
469.}
470.
471.// 讀者
472.class Reader {
473.
474. // 每位讀者最多可同時借出 3 本書
475. public static final int MAX_BORROW = 3;
476.
477. private String name;
478.
479. private int borowCount = 0;
480.
481. public int getBorowCount() {
482. return borowCount;
483. }
484.
485. Reader(String name) {
486. this.name = name;
487. }
488.
489. public String getName() {
490. return name;
491. }
492.
493. public void addBorrowCount() {
494. borowCount++;
495. }
496.
497. public void reduceBorrowCount() {
498. borowCount--;
499. }
500.
501. public boolean canBorrow() {
502. return borowCount < MAX_BORROW;
503. }
504.
505. @Override
506. public String toString() {
507. return name;
508. }
509.}