当前位置: 首页 > news >正文

从设计到落地:校园图书馆系统的面向对象实现全流程

很多小白学面向对象时总困惑:“类图、用例图我会画,但怎么把这些设计变成能跑的代码?” 这篇文章就用 “校园图书馆管理系统” 当例子,从需求分析→设计方案→代码实现→测试验证,带你走通 “设计→实现” 的完整链路,每个步骤都贴代码、讲逻辑,看完你会明白:面向对象实现不是 “凭空写代码”,而是 “把设计蓝图拆成可执行的代码块”

一、先做设计:给实现画好 “施工图纸”

在写一行代码前,先明确 “要做什么” 和 “怎么组织代码”—— 这就是设计阶段的核心。我们先从需求切入,再输出设计成果(类图、交互逻辑),为后续实现打基础。

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 ()(计算超时天数)

关联图书和读者,记录状态

设计时的关键思考:

  1. 继承处理 “图书” 的共性与个性:纸质书和电子书都有编号 / 名称,所以 EBook 继承 Book,只加特有的 url
  1. 关联表示 “谁借了谁”:BorrowRecord 里包含 Book 和 Reader 对象,体现 “借阅” 这个关系
  1. 多态预留扩展:比如不同读者(学生 / 老师)的借阅期限不同,后续实现时让 borrowBook () 方法根据 readerType 返回不同 dueTime

二、从设计到实现第一步:选对语言

设计好 “图纸” 后,先选实现语言。结合案例需求(需要清晰的类继承、强类型校验,小白易上手),我们选Java—— 原因有 3 个:

  1. 面向对象特性纯粹:类、继承、接口的语法清晰,能精准还原设计的类关系
  1. 生态友好:有现成的工具类(如java.util.Date处理借阅时间),不用重复造轮子
  1. 代码可读性高:小白能轻松看懂 “设计的类怎么变成代码”

如果你的项目是快速原型(比如图书馆临时统计工具),也可以选 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);

}

}

六、小结:设计→实现的核心逻辑

看完图书馆系统的案例,你会发现 “面向对象实现” 的本质是:

  1. 先有设计,后有代码:设计阶段定好 “有哪些类、类之间怎么协作”,实现时只是把这些逻辑翻译成代码
  1. 设计是 “蓝图”,实现是 “盖房子”:比如设计时的 “继承关系”→实现时的extends,设计时的 “多态需求”→实现时的条件判断或接口,设计时的 “关联关系”→实现时的对象引用
  1. 小白也能落地:选贴近生活的案例,每一步都对应设计目标,遇到细节问题(如时间计算、校验逻辑)用工具类或简单判断解决,不用一开始就追求复杂设计

如果你跟着敲一遍代码,会更有感觉 —— 下次再做面向对象项目,先画个简单的类图,再按 “基础类→交互逻辑→测试” 的顺序落地,就能轻松从设计走到实现!

还想看更多干货,关注同名公众昊“奈奈聊成长”!!!

http://www.dtcms.com/a/366868.html

相关文章:

  • 多个docker compose启动的容器之间通信实现
  • Oracle 数据库如何查询列
  • (论文速读)Navigation World Models: 让机器人像人类一样想象和规划导航路径
  • 子串:最小覆盖子串
  • 深度学习中的学习率优化策略详解
  • UE5 制作游戏框架的部分经验积累(持续更新)
  • Kubernetes知识点(三)
  • AWS中为OpsManage配置IAM权限:完整指南
  • 深入剖析Spring Boot / Spring 应用中可自定义的扩展点
  • 力扣654:最大二叉树
  • AI+Java 守护你的钱袋子!金融领域的智能风控与极速交易
  • .NET 开发者的“Fiddler”:Titanium.Web.Proxy 库的强大魅力
  • 以数据与自动化驱动实验室变革:智能化管理整体规划
  • “乾坤大挪移”:耐达讯自动化RS485转Profinet解锁HMI新乾坤
  • 数据安全章节考试考点及关系梳理
  • Hadoop(七)
  • 服务器数据恢复—服务器断电,RAID数据恢复大揭秘
  • Python - 通用滑块验证码识别库 Captcha-Recognizer
  • MySQL复制技术的发展历程
  • 【论文阅读—深度学习处理表格数据】ResNet-like FT Transformer
  • 当电力设计遇上AI:良策金宝AI如何重构行业效率边界?
  • 学习嵌入式的第三十三天——网络编程
  • HTB Sau
  • 服务器异常磁盘写排查手册 · 已删除文件句柄篇
  • 稠密矩阵和稀疏矩阵的对比
  • C++面试突击(1)
  • 【面试】MySQL 面试常见优化问题
  • 面试官:如何确保动态线程池任务都执行完?
  • 计算机网络模型入门指南:分层原理与各层作用
  • EasyExcel:阿里开源的高效 Excel 处理工具,轻松解决 POI 内存溢出问题