C++ 任务管理系统设计实战:需求分析、核心类建模与 TaskManager 实现详解(awesome-low-level-design)

原创2026-09-30 14:05:391,767 阅读
文章标签:示例工程

C++ 任务管理系统设计实战:需求分析、核心类建模与 TaskManager 实现详解(awesome-low-level-design)

本文基于开源仓库 awesome-low-level-design 中 C++ 任务管理系统实现 的完整设计文档,逐步拆解一个任务管理系统(Task Management System)的需求分析、类与枚举建模,并结合仓库源码(Task / User / TaskManager 及演示程序)深入讲解每个类的职责、关键方法的调用链与边界处理。读完本文,你将掌握一个典型的 Low Level Design 面试题从需求到 C++ 源码落地的完整过程,并理解依赖任务的状态流转、任务分配的一致性维护等实战细节。

需求分析:一个任务管理系统需要具备哪些能力

设计文档 README 开篇给出了 7 条核心需求,它们共同决定了后续类设计的方向:

  1. 任务生命周期管理:系统应允许用户创建、更新和删除任务;
  2. 任务属性完整:每个任务应包含标题(title)、描述(description)、截止日期(due date)、优先级(priority)和状态(status,如 pending、in progress、completed);
  3. 协作与提醒:用户应能将任务分配给其他用户,并为任务设置提醒;
  4. 检索能力:系统应支持基于多种条件(如优先级、截止日期、被分配用户)搜索和过滤任务;
  5. 状态推进与历史:用户应能将任务标记为已完成,并查看其任务历史;
  6. 并发一致性:系统应能处理任务的并发访问并保证数据一致性;
  7. 可扩展性:系统应易于扩展,以便容纳未来的功能增强。

在 C++ 实现中,这些需求被映射为 User、Task、TaskManager 三个核心类以及 TaskStatus、TaskPriority 两个枚举,入口演示由 TaskManagerDemo 承担。仓库根目录的 problems/task-management-system.md 还给出了该题的标准 UML 类图(taskmanagementsystem-class-diagram.png),可用于对照源码理解整体结构。

任务管理系统 UML 类图

枚举设计:任务状态与优先级

在 Task.hpp 中,任务状态与优先级被建模为两个 enum class(C++11 强类型枚举,避免与整型隐式混用):

enum class TaskStatus {
    TODO,
    IN_PROGRESS,
    COMPLETED,
    BLOCKED
};

enum class TaskPriority {
    LOW,
    MEDIUM,
    HIGH,
    URGENT
};
  • TaskStatus 相比设计文档举例的"pending / in progress / completed"多了一个 BLOCKED 状态,用于表达任务因依赖未满足而无法推进的场景;
  • TaskPriority 提供四档优先级(低/中/高/紧急),MEDIUM 作为构造任务的默认值(见 TaskManager.hpp 的默认参数 TaskPriority::MEDIUM)。

状态与优先级的可读输出在 Task.cpp 的 displayInfo() 中通过 switch 完成,便于调试与演示。

核心类建模:User 与 Task

User:用户实体的最小闭环

User.hpp 中的 User 类持有一个用户的最小信息集:

class User {
private:
    std::string userId;
    std::string username;
    std::string email;
    std::vector<std::string> assignedTasks;  // Task IDs
    bool active;
    ...
};

要点:

  • userId 由 TaskManager 统一生成(格式 U1、U2…,见 TaskManager.cpp),Task 一侧只保存用户 ID 字符串而非对象指针,两者通过 ID 解耦;
  • assignedTasks 保存的是任务 ID 列表,属于"User 知道它被分配了哪些任务"的反向索引,便于 TaskManager::displayUserTasks 直接列出某个用户的任务,对应需求"按被分配用户过滤任务";
  • addTask/removeTask 在 User.cpp 中用 std::find 做去重,保证同一任务不会重复出现在用户的分配列表里。

Task:任务的完整属性与自维护结构

Task.hpp 中的 Task 类对应需求 2 的完整属性集:

class Task {
private:
    std::string taskId;
    std::string title;
    std::string description;
    std::string assignedTo;
    TaskStatus status;
    TaskPriority priority;
    std::time_t dueDate;
    std::vector<std::string> dependencies;  // Task IDs
    std::vector<std::string> comments;
    ...
};

值得注意的实现细节:

  • 截止日期:dueDate 使用 std::time_t 表示,构造函数将其初始化为 0 表示"未设置截止日期"(Task.cpp),输出时仅当非 0 才打印(Task.cpp);
  • 任务依赖:dependencies 存放前置任务 ID,addDependency 在 Task.cpp 同样做了去重,这是后续"依赖未完成则不能开工"状态机的基础;
  • 评论:comments 支持为任务追加评论,对应设计文档中"设置提醒/备注"的协作诉求;
  • 新任务默认状态为 TODO(Task.cpp)。

核心控制器:TaskManager 的设计权衡

设计文档明确指出 TaskManager 是系统的核心,并描述了两种关键设计决策:

  • 单例模式:确保全局只有一个任务管理器实例;
  • 并发安全:使用 ConcurrentHashMap 与 CopyOnWriteArrayList 处理并发访问(这是 Java 版实现的线程安全策略)。

从源码结构看,C++ 版本做了取舍:TaskManager 采用普通类而非单例,存储使用 std::vector(TaskManager.hpp):

class TaskManager {
private:
    std::vector<Task*> tasks;
    std::vector<User*> users;
    int taskIdCounter;
    int userIdCounter;
    ...
};

这带来两点可以明确说明的事实:

  1. README 中关于 ConcurrentHashMap/CopyOnWriteArrayList 的表述描述的是 Java 实现的并发策略;C++ 实现面向教学演示,选择了最直观的 std::vector 线性存储,并未引入锁机制。读者在工程化时应自行叠加 std::mutex 或改用 std::shared_mutex;
  2. 尽管未使用单例,TaskManager 仍通过 ID 计数器(T1/U1 自增)集中管理身份分配,承担了"唯一入口"的职责;如需单例化,仅需将构造私有化并增加 getInstance(),类的职责边界无需改动——这正是需求 7"可扩展性"的体现。

TaskManager 的方法集覆盖了设计文档要求的全部能力,下面按职责拆解。

用户与任务的创建/删除

  • registerUser(TaskManager.cpp)生成用户 ID、构造 User 并加入 users;
  • createTask(TaskManager.cpp)生成任务 ID 并以 MEDIUM 为默认优先级构造 Task;
  • removeUser(TaskManager.cpp)在删除用户前,会先遍历所有任务,把分配给该用户的任务的 assignedTo 清空(置为 ""),避免出现悬空的分配关系;
  • removeTask(TaskManager.cpp)是一个一致性维护的范例:删除任务时同步执行三步清理——① 从被分配用户的 assignedTasks 中移除该任务;② 从所有其他任务的 dependencies 中移除对该任务的依赖引用;③ 再释放内存并从 tasks 中移除。

任务分配的一致性

assignTask(TaskManager.cpp)演示了双向索引的同步维护:

// 先把任务从前任被分配者名下移除
if (!task->getAssignedTo().empty()) {
    if (User* prevUser = findUser(task->getAssignedTo())) {
        prevUser->removeTask(taskId);
    }
}
task->setAssignedTo(userId);   // Task 侧记录被分配者
user->addTask(taskId);         // User 侧记录任务

只有任务与被分配者都存在时才返回 true,否则返回 false,保证了 Task.assignedTo 与 User.assignedTasks 永远保持对称,对应需求"将任务分配给其他用户"。

依赖驱动的状态机

updateTaskStatus(TaskManager.cpp)是本实现中最有业务价值的逻辑:只有当任务的全部依赖都处于 COMPLETED 状态时,任务才允许进入 IN_PROGRESS:

if (status == TaskStatus::IN_PROGRESS && !checkDependenciesMet(task)) {
    std::cout << "Cannot start task: dependencies not met" << std::endl;
    return false;
}
task->setStatus(status);

checkDependenciesMet(TaskManager.cpp)逐个校验依赖任务:依赖不存在或依赖状态非 COMPLETED 都视为"依赖未满足"。这一机制让 BLOCKED 语义落到实处——依赖未完成的任务无法开工,保证了任务推进顺序的正确性。

搜索与过滤

设计文档要求支持按条件搜索过滤任务,C++ 实现提供了两个方法:

配合 displayUserTasks(按用户列出)与 displayAllTasks(列出全部并带分隔线),四类查询方式恰好覆盖了"按优先级、按状态、按被分配用户"的检索需求。

演示程序:从注册用户到依赖任务全流程

入口文件 TaskManagerDemo.cpp 完整演示了系统的运行闭环,流程如下:

  1. 注册用户:注册 john_doe 与 alice_smith 两名用户,并调用 displayAllUsers() 输出初始状态(L8-L12);
  2. 创建任务:创建 "Design Database"(HIGH)、"Implement API"(MEDIUM)、"Write Tests"(MEDIUM)三个任务(L15-L17);
  3. 建立依赖链:addTaskDependency 形成 DB → API → Tests 的链式依赖(L20-L21):
    • "Implement API" 依赖 "Design Database";
    • "Write Tests" 依赖 "Implement API";
  4. 分配任务:任务 1 分给 john,任务 2、3 分给 alice(L24-L26);
  5. 验证依赖拦截:将任务 1 置为 IN_PROGRESS 并加评论;随后尝试把任务 2 置为 IN_PROGRESS,由于依赖(任务 1)尚未完成,该调用会被拒绝并打印 "Cannot start task: dependencies not met"(L29-L33);
  6. 完成前置任务后放行:将任务 1 置为 COMPLETED 后,任务 2 才能成功进入 IN_PROGRESS(L36-L40);
  7. 结果展示:分别输出全部任务、按用户分组的任务、以及处于 IN_PROGRESS 状态的任务列表(L43-L55)。

这一流程直观展示了"依赖未满足 → 阻塞开工 → 依赖完成 → 放行"的完整状态流转,是阅读 updateTaskStatus 与 checkDependenciesMet 实现的最佳入口。

编译与运行

该实现目录下没有 CMakeLists.txt 或 Makefile,四个源文件(Task.cpp、User.cpp、TaskManager.cpp、TaskManagerDemo.cpp)与对应头文件直接组成一个可执行程序。可以推断直接用任意支持 C++11 及以上的编译器即可构建,例如:

g++ -std=c++17 -o task_manager_demo \
    Task.cpp User.cpp TaskManager.cpp TaskManagerDemo.cpp
./task_manager_demo

运行输出将包含用户注册信息、三任务创建结果、依赖拦截提示("Cannot start task: dependencies not met")以及按用户/按状态过滤后的任务清单。

与仓库其他语言实现的横向对照

该问题在仓库中属于多语言实现的标准例题(problems/task-management-system.md),除 C++ 外还有:

其中 README 提到的 ConcurrentHashMap/CopyOnWriteArrayList 线程安全方案,正是 Java 实现 中 TaskManager 的具体落地;对比阅读 C++ 版(std::vector + 手动维护一致性)与 Java 版(并发容器 + 单例)的差异,可以更深刻地理解"同一设计文档在不同语言下的实现取舍"——这正是本仓库作为 LLD 面试准备的独特价值:需求与类设计是通用的,而线程安全与存储策略需要结合语言特性因地制宜。

小结

本仓库的 C++ 任务管理系统实现 用不足百行的核心代码,完整覆盖了设计文档中的 7 条需求:User 与 Task 提供属性建模,TaskStatus/TaskPriority 提供状态与优先级语义,TaskManager 以集中式控制器完成创建/删除/分配/状态流转/过滤查询,并以依赖检查实现了"前置任务未完成则不能开工"的业务规则,同时通过删除时的引用清理保证了数据一致性。对照设计文档与源码逐行研读,是掌握 LLD 面试"需求 → 类图 → 代码"全链路的最佳方式。

登录后查看全文
awesome-low-level-design