用 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.}