1. 引言
欢迎来到我们 C# 面向对象编程(OOP)博客系列的最后一篇!在本系列中,我们通过实际案例探索了 OOP 的基础知识、高级概念以及设计模式。现在是时候将所有内容结合起来,应用于一个真实场景中。
在这篇文章中,我们将解决一个常见的面向对象设计面试问题:设计一个电子书阅读器应用程序。这是一个绝佳的机会,展示如何运用 OOP 原则高效地解决现实世界中的问题。从识别系统需求到用 C# 实现解决方案,我们将逐步引导你完成整个过程。
无论你是准备技术面试,还是想加深对 OOP 的理解,这篇文章都将为你提供宝贵的见解和实用技巧。让我们开始了解问题描述并设计解决方案吧!
2. 问题描述
你被要求设计一个电子书阅读器应用程序。该应用程序应允许用户管理其数字图书馆、阅读书籍并跟踪其阅读进度。以下是具体需求:
功能需求
-
图书馆管理:
- 用户可以将书籍添加到个人图书馆中。
- 用户可以从图书馆中移除书籍。
-
阅读功能:
- 用户可以从图书馆中选择一本书作为当前阅读书籍。
- 应用程序应每次显示一页文字内容。
- 用户可以在页面之间导航(上一页/下一页)。
- 应保存阅读进度(例如,记住最后阅读的页面)。
假设条件
- 初始实现专注于单用户场景(不考虑多用户)。
- 电子书内容为纯文本,无复杂格式(如字体大小等)。
3. 系统设计
高级架构
系统将包含以下三个主要组件:
- Library(图书馆管理器): 负责管理用户图书馆中的书籍集合。
- BookReader(书籍阅读器): 处理阅读功能,包括页面导航和书签。
- Book(书籍): 表示单个电子书及其内容。
关键类及其职责
-
Library(图书馆):
- 管理用户的书籍集合。
- 方法:
AddBook(Book book): 将一本书添加到图书馆中。RemoveBook(string title): 根据标题从图书馆中移除一本书。GetBook(int bookId): 根据 bookId 获取一本书。
-
BookReader(书籍阅读器):
- 处理当前的阅读会话。
- 方法:
OpenBook(Book book): 设置当前阅读的书籍。NextPage(): 跳转到下一页。PreviousPage(): 跳转到上一页。GetCurrentPage(): 获取当前页面的内容。BookmarkPage(): 保存当前页面为书签。
-
Book(书籍):
- 表示一本电子书。
- 属性:
Title: 书籍标题。BookId: 书籍 ID。Author: 书籍作者。Content: 书籍的纯文本内容,按页分割。Bookmark: 上次阅读的页码。
- 方法:
GetPage(int pageNumber): 获取指定页码的内容。
数据流
- 用户通过 Library 添加或移除书籍。
- 当用户选择一本书时,该书会被传递给 BookReader 以进行阅读。
- BookReader 从 Book 类中获取内容,并负责管理页面导航和书签功能。
示例流程:
- 用户通过
Library.AddBook()添加一本新书到图书馆。 - 用户选择一本书进行阅读,触发
BookReader.OpenBook()。 - BookReader 使用
Book.GetPage(1)显示第一页内容。 - 用户通过
BookReader.NextPage()导航到下一页。 - 用户通过
BookReader.BookmarkPage()保存当前页面为书签。
4. 用 C# 实现
下面是电子书阅读器应用程序核心功能的分步实现:
第一步:定义 Book 类
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public List<string> Content { get; set; } // 按页分割的内容
public int Bookmark { get; set; } // 最后阅读的页码
public Book(int bookId, string title, string author, List<string> content)
{
BookId = bookId;
Title = title;
Author = author;
Content = content;
Bookmark = 0; // 从第一页开始
}
public string GetPage(int pageNumber)
{
if (pageNumber < 0 || pageNumber >= Content.Count)
{
return "无效的页码。";
}
return Content[pageNumber];
}
}
第二步:定义 Library 类
public class Library
{
private List<Book> Library = new List<Book>();
public void AddBook(Book book)
{
Library.Add(book);
}
public void RemoveBook(int bookId)
{
Library.RemoveAll(b => b.BookId == bookId);
}
public Book GetBook(int bookId)
{
return Library.FirstOrDefault(b => b.BookId == bookId);
}
}
第三步:定义 BookReader 类
public class BookReader
{
private Book ActiveBook;
private int CurrentPage;
public void OpenBook(Book book)
{
ActiveBook = book;
CurrentPage = book.Bookmark;
}
public string GetCurrentPage()
{
return ActiveBook?.GetPage(CurrentPage) ?? "当前没有打开的书籍。";
}
public void NextPage()
{
if (ActiveBook != null && CurrentPage < ActiveBook.Content.Count - 1)
{
CurrentPage++;
}
}
public void PreviousPage()
{
if (ActiveBook != null && CurrentPage > 0)
{
CurrentPage--;
}
}
public void BookmarkPage()
{
if (ActiveBook != null)
{
ActiveBook.Bookmark = CurrentPage;
}
}
}
代码解释
-
Book 类:
- 表示一本电子书,包含 id、标题、作者、内容和书签等属性。
- 使用
GetPage()方法获取特定页的内容。
-
Library 类:
- 管理书籍集合,包含添加、移除和通过 bookId 获取书籍的方法。
-
BookReader 类:
- 管理当前的阅读会话,包括页面导航和书签功能。
- 与
Book类交互以获取和显示内容。
5. 数据库设计
为了支持应用程序的功能,系统需要两种不同的存储解决方案:
1. 元数据存储(关系型数据库)
存储关于图书馆、书籍和用户阅读进度的信息。
表设计
Library 表(图书馆表):
LibraryId(主键):每个图书馆的唯一标识符。BookIds(JSON 格式):包含图书馆中书籍BookId的 JSON 数组。UserId:可选字段,为未来的多用户支持预留。
| LibraryId | BookIds | UserId |
|---|---|---|
| 1 | [101, 102, 103] | null |
| 2 | [104, 105] | null |
Book 表(书籍表):
BookId(主键):每本书的唯一标识符。Title:书籍标题。Author:书籍作者。Bookmark:最后阅读的页码(阅读进度)。
| BookId | Title | Author | Bookmark |
|---|---|---|---|
| 101 | "C# 基础" | "John Smith" | 5 |
| 102 | "OOP 设计" | "Jane Doe" | 12 |
| 103 | "系统设计" | "Alex White" | 0 |
2. 内容存储(文件系统或对象存储)
存储书籍的实际内容。每本书的内容按页分割并保存在文件系统或对象存储(如 AWS S3、Azure Blob Storage)中。
文件命名规则
- 文件使用
BookId命名以便于检索。 - 示例:
101.txt,102.txt
目录结构:
/content_storage/
101.txt
102.txt
103.txt
工作流程
- 当用户将一本书添加到图书馆时,该书的
BookId会被追加到Library表中的BookIdsJSON 数组中。 - 当用户打开一本书时,系统从
Book表中检索其元数据,并从文件系统中获取其内容文件。 - 阅读进度(如书签)会更新到
Book表中。
为了与数据库交互,我们需要定义 数据访问对象(DAO) 类来处理数据的保存和查询操作。DAO 层将包含将关系数据映射为对象以及对象映射回关系数据的方法。
6. 总结
关键要点
- 理解并应用面向对象的原则对设计可扩展和可维护的系统至关重要。
- 使用单例模式(Singleton)、工厂模式(Factory)和观察者模式(Observer)等设计模式可以显著提升应用程序的设计质量。
- 数据库设计和 DAO 类在高效管理持久化数据方面起着关键作用。
应对类似面试问题的技巧
- 将问题分解为较小、可管理的部分。
- 专注于需求,确保您的解决方案满足需求。
- 使用 UML 图或伪代码清晰地传达您的思路。
- 强调设计模式的使用并解释您的设计决策。
通过这种方法,您将能够更好地应对技术面试中的面向对象设计问题。