很多小白學面向對象時總困惑:“類圖、用例圖我會畫,但怎么把這些設計變成能跑的代碼?” 這篇文章就用 “校園圖書館管理系統” 當例子,從需求分析→設計方案→代碼實現→測試驗證,帶你走通 “設計→實現” 的完整鏈路,每個步驟都貼代碼、講邏輯,看完你會明白:面向對象實現不是 “憑空寫代碼”,而是 “把設計藍圖拆成可執行的代碼塊”。
一、先做設計:給實現畫好 “施工圖紙”
在寫一行代碼前,先明確 “要做什么” 和 “怎么組織代碼”—— 這就是設計階段的核心。我們先從需求切入,再輸出設計成果(類圖、交互邏輯),為后續實現打基礎。
1.1 需求分析:明確核心角色與功能
校園圖書館系統的核心需求很清晰:
- 讀者(學生 / 老師)能查圖書、借圖書、還圖書,老師的借閱期限比學生長
- 圖書有基本信息(編號、名稱、作者、狀態:可借 / 已借),電子圖書還需要 “電子版鏈接”
- 系統要記錄借閱記錄(誰借了哪本書、借出時間、應還時間、是否超時)
- 管理員能添加圖書、查詢所有借閱記錄
1.2 面向對象設計:定義 “類” 與 “關系”
根據需求,我們設計出 4 個核心類,用簡單的類圖(非專業 UML,小白也能懂)展示設計結果:
類名 | 屬性(設計時定義) | 方法(設計時定義) | 備注 |
Book(圖書) | bookId (編號)、name (名稱)、author (作者)、status (狀態) | getStatus()、setStatus()、getInfo() | 父類,包含所有圖書共性 |
EBook(電子圖書) | 繼承 Book 的所有屬性 + url (電子版鏈接) | 繼承 Book 的方法 + getUrl () | 子類,擴展電子圖書特性 |
Reader(讀者) | readerId (學號 / 工號)、name (姓名)、type (類型:學生 / 老師) | borrowBook ()(借書)、returnBook ()(還書) | 抽象出讀者的核心行為 |
BorrowRecord(借閱記錄) | recordId (記錄 ID)、book (關聯圖書)、reader (關聯讀者)、borrowTime (借出時間)、dueTime (應還時間) | calculateOverdue ()(計算超時天數) | 關聯圖書和讀者,記錄狀態 |
設計時的關鍵思考:
- 用繼承處理 “圖書” 的共性與個性:紙質書和電子書都有編號 / 名稱,所以 EBook 繼承 Book,只加特有的 url
- 用關聯表示 “誰借了誰”:BorrowRecord 里包含 Book 和 Reader 對象,體現 “借閱” 這個關系
- 用多態預留擴展:比如不同讀者(學生 / 老師)的借閱期限不同,后續實現時讓 borrowBook () 方法根據 readerType 返回不同 dueTime
二、從設計到實現第一步:選對語言
設計好 “圖紙” 后,先選實現語言。結合案例需求(需要清晰的類繼承、強類型校驗,小白易上手),我們選Java—— 原因有 3 個:
- 面向對象特性純粹:類、繼承、接口的語法清晰,能精準還原設計的類關系
- 生態友好:有現成的工具類(如java.util.Date處理借閱時間),不用重復造輪子
- 代碼可讀性高:小白能輕松看懂 “設計的類怎么變成代碼”
如果你的項目是快速原型(比如圖書館臨時統計工具),也可以選 Python,但 Java 更適合展示 “設計→實現” 的嚴謹性。
三、核心功能落地:把設計圖寫成代碼
這部分是重點!我們按 “先實現基礎類→再實現交互邏輯” 的順序,一步步把設計轉化為代碼,每段代碼都標注 “對應設計的哪部分”。
3.1 第一步:實現基礎類(Book + EBook)
設計時定義了 Book 的屬性和方法,現在用 Java 代碼落地,重點體現 “封裝” 和 “繼承”:
// 1. 實現Book類(對應設計中的“圖書父類”)
public class Book {
// 設計中的屬性:用private封裝,隱藏內部狀態(符合“封裝”設計)
private String bookId;
private String name;
private String author;
// 狀態:0=可借,1=已借(設計時定義的狀態規則)
private int status;
// 構造方法:創建Book對象時必須傳入核心屬性(設計時的“必填信息”)
public Book(String bookId, String name, String author) {
this.bookId = bookId;
this.name = name;
this.author = author;
this.status = 0; // 新書默認“可借”
}
// 設計中的方法:getter/setter(控制屬性訪問,符合封裝)
public int getStatus() {
return status;
}
// 改狀態時加校驗:只能設為0或1(設計時沒寫,但實現時要補細節)
public void setStatus(int status) {
if (status == 0 || status == 1) {
this.status = status;
} else {
throw new IllegalArgumentException("狀態只能是0(可借)或1(已借)");
}
}
// 設計中的getInfo():返回圖書信息(方便后續展示)
public String getInfo() {
String statusStr = status == 0 ? "可借" : "已借";
return "編號:" + bookId + ",書名:" + name + ",作者:" + author + ",狀態:" + statusStr;
}
// getter(name和bookId需要被其他類訪問,比如BorrowRecord)
public String getBookId() {
return bookId;
}
public String getName() {
return name;
}
}
// 2. 實現EBook類(對應設計中的“電子圖書子類”)
public class EBook extends Book {
// 設計中的擴展屬性:電子版鏈接
private String url;
// 構造方法:先調用父類構造(必須初始化父類屬性),再初始化子類屬性
public EBook(String bookId, String name, String author, String url) {
super(bookId, name, author); // 調用Book的構造方法(繼承的核心)
this.url = url;
}
// 設計中的擴展方法:獲取電子版鏈接
public String getUrl() {
return url;
}
// 重寫getInfo():在父類基礎上增加url(體現多態的“重寫”特性)
@Override
public String getInfo() {
// 先復用父類的getInfo(),再加子類信息(設計時的“擴展不修改”原則)
return super.getInfo() + ",電子版鏈接:" + url;
}
}
設計→實現的關鍵轉化點:
- 設計時的 “封裝”:用private修飾屬性,只通過setter改狀態,還加了校驗(比如狀態只能是 0/1),避免非法值 —— 這是把 “設計的安全性要求” 落地
- 設計時的 “繼承”:EBook extends Book,用super()調用父類構造,復用了 Book 的屬性和方法 —— 這是把 “設計的共性復用” 落地
3.2 第二步:實現讀者類(Reader)
設計時 Reader 有 “借書 / 還書” 方法,實現時要結合 “多態”(不同讀者借閱期限不同):
import java.util.Date;
import java.util.Calendar;
public class Reader {
private String readerId;
private String name;
// 設計中的類型:0=學生,1=老師
private int type;
public Reader(String readerId, String name, int type) {
this.readerId = readerId;
this.name = name;
// 實現時補校驗:只能是學生或老師
if (type != 0 && type != 1) {
throw new IllegalArgumentException("讀者類型只能是0(學生)或1(老師)");
}
this.type = type;
}
// 設計中的核心方法:借書(返回借閱記錄,方便后續保存)
public BorrowRecord borrowBook(Book book) {
// 第一步:校驗圖書狀態(設計時的“借閱前校驗”)
if (book.getStatus() == 1) {
throw new RuntimeException("圖書《" + book.getName() + "》已被借出");
}
// 第二步:改圖書狀態為“已借”(設計時的“狀態更新”)
book.setStatus(1);
// 第三步:計算應還時間(設計時的“多態需求”:學生30天,老師60天)
Date borrowTime = new Date(); // 當前時間作為借出時間
Calendar calendar = Calendar.getInstance();
calendar.setTime(borrowTime);
// 按讀者類型加天數(實現多態的“差異化邏輯”)
int days = type == 0 ? 30 : 60;
calendar.add(Calendar.DAY_OF_MONTH, days);
Date dueTime = calendar.getTime();
// 第四步:創建借閱記錄(設計時的“關聯關系”)
return new BorrowRecord(
"REC" + System.currentTimeMillis(), // 簡單生成唯一記錄ID
book,
this,
borrowTime,
dueTime
);
}
// 設計中的核心方法:還書(更新圖書狀態和記錄)
public void returnBook(Book book, BorrowRecord record) {
// 校驗:確保是自己借的書(實現時補的嚴謹性邏輯)
if (!record.getReader().getReaderId().equals(this.readerId)) {
throw new RuntimeException("不能歸還他人借閱的圖書");
}
// 改圖書狀態為“可借”(設計時的“狀態回滾”)
book.setStatus(0);
// 計算超時(調用BorrowRecord的方法,體現類間協作)
int overdueDays = record.calculateOverdue();
if (overdueDays > 0) {
System.out.println("警告:《" + book.getName() + "》已超時" + overdueDays + "天,請盡快處理!");
} else {
System.out.println("圖書《" + book.getName() + "》歸還成功,無超時");
}
}
// getter(供其他類訪問)
public String getReaderId() {
return readerId;
}
public String getName() {
return name;
}
}
設計→實現的關鍵轉化點:
- 設計時的 “多態”:通過type == 0 ? 30 : 60實現不同讀者的期限差異,沒有用復雜的接口,小白能快速理解
- 設計時的 “類間協作”:borrowBook方法里調用Book的setStatus,創建BorrowRecord對象 —— 這是把 “設計的交互邏輯” 落地為代碼調用
3.3 第三步:實現借閱記錄類(BorrowRecord)
設計時這個類要 “計算超時天數”,實現時用 Java 的日期工具類完成:
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class BorrowRecord {
private String recordId;
private Book book; // 關聯Book對象(設計時的“關聯關系”)
private Reader reader; // 關聯Reader對象(設計時的“關聯關系”)
private Date borrowTime;
private Date dueTime;
// 構造方法(初始化所有屬性)
public BorrowRecord(String recordId, Book book, Reader reader, Date borrowTime, Date dueTime) {
this.recordId = recordId;
this.book = book;
this.reader = reader;
this.borrowTime = borrowTime;
this.dueTime = dueTime;
}
// 設計中的核心方法:計算超時天數(當前時間 - 應還時間)
public int calculateOverdue() {
Date now = new Date();
// 如果沒到應還時間,超時天數為0
if (now.before(dueTime)) {
return 0;
}
// 計算毫秒差,轉成天數(實現時的工具類運用)
long diffMs = now.getTime() - dueTime.getTime();
return (int) TimeUnit.DAYS.convert(diffMs, TimeUnit.MILLISECONDS);
}
// getter(供其他類訪問關聯對象)
public Book getBook() {
return book;
}
public Reader getReader() {
return reader;
}
// 展示記錄信息(方便測試和查看)
public String getRecordInfo() {
return "借閱記錄ID:" + recordId +
",讀者:" + reader.getName() +
",圖書:" + book.getName() +
",應還時間:" + dueTime;
}
}
3.4 第四步:實現系統入口(模擬真實使用)
設計時的 “管理員添加圖書、讀者借書” 等功能,用一個LibrarySystem類模擬:
import java.util.ArrayList;
import java.util.List;
public class LibrarySystem {
// 系統存儲的圖書和借閱記錄(設計時的“數據存儲”需求)
private List<Book> bookList = new ArrayList<>();
private List<BorrowRecord> recordList = new ArrayList<>();
// 管理員添加圖書(設計時的“管理員功能”)
public void addBook(Book book) {
// 校驗:圖書編號唯一(實現時補的業務規則)
boolean isDuplicate = bookList.stream()
.anyMatch(b -> b.getBookId().equals(book.getBookId()));
if (isDuplicate) {
throw new RuntimeException("圖書編號" + book.getBookId() + "已存在");
}
bookList.add(book);
System.out.println("添加圖書成功:" + book.getInfo());
}
// 讀者借書(調用Reader的方法,保存記錄)
public void borrowBook(Reader reader, String bookId) {
// 先找到對應圖書(實現時的“查詢邏輯”)
Book targetBook = bookList.stream()
.filter(b -> b.getBookId().equals(bookId))
.findFirst()
.orElseThrow(() -> new RuntimeException("未找到編號為" + bookId + "的圖書"));
// 調用Reader的borrowBook方法(類間協作的落地)
BorrowRecord record = reader.borrowBook(targetBook);
recordList.add(record);
System.out.println("借書成功!" + record.getRecordInfo());
}
// 讀者還書(調用Reader的方法)
public void returnBook(Reader reader, String bookId) {
Book targetBook = bookList.stream()
.filter(b -> b.getBookId().equals(bookId))
.findFirst()
.orElseThrow(() -> new RuntimeException("未找到編號為" + bookId + "的圖書"));
// 找到該讀者的借閱記錄(實現時的“關聯查詢”)
BorrowRecord targetRecord = recordList.stream()
.filter(r -> r.getReader().getReaderId().equals(reader.getReaderId())
&& r.getBook().getBookId().equals(bookId))
.findFirst()
.orElseThrow(() -> new RuntimeException("未找到" + reader.getName() + "借閱《" + targetBook.getName() + "》的記錄"));
// 調用Reader的returnBook方法
reader.returnBook(targetBook, targetRecord);
}
// 測試入口(模擬真實場景)
public static void main(String[] args) {
// 1. 創建系統
LibrarySystem library = new LibrarySystem();
// 2. 管理員添加圖書(紙質書+電子書)
Book book1 = new Book("B001", "《Java編程思想》", "Bruce Eckel");
EBook book2 = new EBook("B002", "《Python編程:從入門到實踐》", "Eric Matthes", "https://example.com/ebook2");
library.addBook(book1);
library.addBook(book2);
// 3. 創建讀者(學生+老師)
Reader student = new Reader("S001", "張三", 0); // 0=學生
Reader teacher = new Reader("T001", "李老師", 1); // 1=老師
// 4. 學生借書
System.out.println("\n=== 學生張三借書 ===");
library.borrowBook(student, "B001"); // 借《Java編程思想》
// 5. 老師借書
System.out.println("\n=== 李老師借書 ===");
library.borrowBook(teacher, "B002"); // 借電子圖書
// 6. 學生還書(模擬超時:這里手動改dueTime會更明顯,實際可通過時間工具模擬)
System.out.println("\n=== 學生張三還書 ===");
library.returnBook(student, "B001");
}
}
運行結果(小白能直觀看到效果):
添加圖書成功:編號:B001,書名:《Java編程思想》,作者:Bruce Eckel,狀態:可借
添加圖書成功:編號:B002,書名:《Python編程:從入門到實踐》,作者:Eric Matthes,狀態:可借,電子版鏈接:https://example.com/ebook2
=== 學生張三借書 ===
借書成功!借閱記錄ID:REC1693800000000,讀者:張三,圖書:《Java編程思想》,應還時間:Wed Oct 04 10:00:00 CST 2025
=== 李老師借書 ===
借書成功!借閱記錄ID:REC1693800000001,讀者:李老師,圖書:《Python編程:從入門到實踐》,應還時間:Fri Nov 03 10:00:00 CST 2025
=== 學生張三還書 ===
圖書《Java編程思想》歸還成功,無超時
四、設計驅動的程序設計風格:讓代碼 “好維護”
實現時不能只寫 “能跑的代碼”,還要結合設計的 “可重用、可擴展” 要求。比如:
4.1 提升可重用性:抽離通用工具
設計時沒考慮 “時間格式化”,但實現時發現BorrowRecord的dueTime顯示太亂,于是抽一個通用工具類:
import java.text.SimpleDateFormat;
import java.util.Date;
// 通用日期工具類(可重用,其他項目也能拿過來用)
public class DateUtils {
// 格式化日期為“yyyy-MM-dd HH:mm:ss”
public static String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
然后在BorrowRecord的getRecordInfo()里用:
public String getRecordInfo() {
return "借閱記錄ID:" + recordId +
",讀者:" + reader.getName() +
",圖書:" + book.getName() +
",應還時間:" + DateUtils.formatDate(dueTime); // 用工具類
}
這樣改后,所有需要格式化日期的地方都能復用DateUtils,符合設計的 “復用” 要求。
4.2 提升可擴展性:預留擴展點
設計時考慮 “未來可能加‘管理員續借’功能”,實現時在BorrowRecord里加一個extendDueTime方法(先不實現完整邏輯,留接口):
// 預留續借方法(設計時的“擴展需求”)
public void extendDueTime(int days) {
if (days <= 0) {
throw new IllegalArgumentException("續借天數必須大于0");
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(this.dueTime);
calendar.add(Calendar.DAY_OF_MONTH, days);
this.dueTime = calendar.getTime();
System.out.println("續借成功!新應還時間:" + DateUtils.formatDate(this.dueTime));
}
未來需要續借功能時,直接調用這個方法,不用改原有代碼 —— 符合 “開閉原則”(設計時的核心原則)。
五、基于設計的測試:驗證實現是否符合預期
測試不是 “隨便點一點”,而是針對設計的功能點驗證。我們用 “單元測試 + 集成測試” 結合案例說明。
5.1 單元測試:測試單個類的方法(以 Book 類為例)
用 JUnit 5 測試Book的setStatus方法,驗證 “非法狀態是否報錯”(對應設計的 “狀態校驗”):
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class BookTest {
// 測試“設置合法狀態”
@Test
public void testSetStatus_Valid() {
Book book = new Book("B001", "測試書", "測試作者");
book.setStatus(1); // 設為“已借”
assertEquals(1, book.getStatus()); // 斷言狀態正確
}
// 測試“設置非法狀態”(預期拋出異常)
@Test
public void testSetStatus_Invalid() {
Book book = new Book("B001", "測試書", "測試作者");
// 斷言設置狀態2時拋出IllegalArgumentException
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
book.setStatus(2);
});
assertEquals("狀態只能是0(可借)或1(已借)", exception.getMessage());
}
}
5.2 集成測試:測試類間協作(以 “借書流程” 為例)
測試 “讀者借書→圖書狀態變→生成記錄” 的完整流程(對應設計的 “交互邏輯”):
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class LibraryIntegrationTest {
@Test
public void testBorrowBook_Flow() {
// 1. 準備環境
LibrarySystem library = new LibrarySystem();
Book book = new Book("B001", "測試書", "測試作者");
library.addBook(book);
Reader student = new Reader("S001", "張三", 0);
// 2. 執行借書操作
library.borrowBook(student, "B001");
// 3. 驗證結果(設計的預期:圖書狀態變1,記錄存在)
assertEquals(1, book.getStatus()); // 圖書已借
// 驗證記錄列表里有這條記錄
boolean hasRecord = library.getRecordList().stream()
.anyMatch(r -> r.getBook().getBookId().equals("B001")
&& r.getReader().getReaderId().equals("S001"));
assertTrue(hasRecord);
}
}
六、小結:設計→實現的核心邏輯
看完圖書館系統的案例,你會發現 “面向對象實現” 的本質是:
- 先有設計,后有代碼:設計階段定好 “有哪些類、類之間怎么協作”,實現時只是把這些邏輯翻譯成代碼
- 設計是 “藍圖”,實現是 “蓋房子”:比如設計時的 “繼承關系”→實現時的extends,設計時的 “多態需求”→實現時的條件判斷或接口,設計時的 “關聯關系”→實現時的對象引用
- 小白也能落地:選貼近生活的案例,每一步都對應設計目標,遇到細節問題(如時間計算、校驗邏輯)用工具類或簡單判斷解決,不用一開始就追求復雜設計
如果你跟著敲一遍代碼,會更有感覺 —— 下次再做面向對象項目,先畫個簡單的類圖,再按 “基礎類→交互邏輯→測試” 的順序落地,就能輕松從設計走到實現!
還想看更多干貨,關注同名公眾昊“奈奈聊成長”!!!