dart学习第 9 节: 基础实战 —— 学生信息管理系统​

85 阅读5分钟

今天,我们将通过一个学生信息管理系统的实战案例,把这些知识串联起来,体验从需求分析到代码实现,再到优化重构的完整过程。

一、需求分析:我们要做什么?

学生信息管理系统的核心需求是实现对学生信息的增删改查(CRUD) ,具体包括:

  1. 添加:录入学生的学号、姓名、年龄等信息
  2. 查询:查看单个学生信息或所有学生列表
  3. 修改:更新学生的姓名、年龄等信息
  4. 删除:移除指定学生的信息
  5. 退出:结束程序运行

我们将用 Dart 控制台程序实现这些功能,重点练习类的设计集合的操作函数的封装



二、核心技术点梳理

在开始编码前,先明确我们会用到的技术:

  • 类(Class) :定义 Student 类封装学生信息
  • List 集合:用 List<Student> 存储所有学生数据
  • 函数封装:将增删改查各功能拆分为独立函数
  • 用户交互:通过控制台输入输出与用户交互
  • 流程控制:用循环和分支实现菜单逻辑


三、代码实现:从 0 到 1 搭建系统

1. 步骤 1:定义学生类(数据模型)

首先,我们需要一个 Student 类来规范学生信息的数据结构,包含学号(唯一标识)、姓名、年龄三个核心属性:

class Student {
  final String id; // 学号(不可变,唯一标识)
  String name; // 姓名
  int age; // 年龄

  // 构造函数
  Student({required this.id, required this.name, required this.age});

  // 重写toString方法,方便打印学生信息
  @override
  String toString() {
    return "学号:$id,姓名:$name,年龄:$age";
  }
}
  • id 设为 final 确保学号不可修改,避免重复或误改
  • 重写 toString() 方便后续打印学生信息时显示清晰格式

2. 步骤 2:初始化数据和工具函数

接下来,我们需要一个存储学生的列表,以及一些工具函数(如判断学号是否存在):

// 存储所有学生的列表
List<Student> students = [];

// 检查学号是否已存在(用于避免重复添加)
bool isIdExists(String id) {
  return students.any((student) => student.id == id);
}

3. 步骤 3:实现增删改查核心功能

将每个操作封装为独立函数,保证单一职责:

(1)添加学生
void addStudent() {
  print("\n===== 添加学生 =====");

  // 输入学号(确保唯一)
  String id;
  while (true) {
    print("请输入学号:");
    id = stdin.readLineSync()?.trim() ?? "";
    if (id.isEmpty) {
      print("学号不能为空!");
    } else if (isIdExists(id)) {
      print("该学号已存在,请重新输入!");
    } else {
      break; // 学号合法,退出循环
    }
  }

  // 输入姓名
  String name;
  while (true) {
    print("请输入姓名:");
    name = stdin.readLineSync()?.trim() ?? "";
    if (name.isEmpty) {
      print("姓名不能为空!");
    } else {
      break;
    }
  }

  // 输入年龄(确保是数字)
  int age;
  while (true) {
    print("请输入年龄:");
    String? ageInput = stdin.readLineSync()?.trim();
    age = int.tryParse(ageInput ?? "") ?? -1;
    if (age <= 0) {
      print("请输入有效的年龄!");
    } else {
      break;
    }
  }

  // 添加到列表
  students.add(Student(id: id, name: name, age: age));
  print("添加成功!学生信息:$students.last");
}
  • 通过循环验证输入合法性(非空、学号唯一、年龄为正数)
  • 输入验证能有效避免脏数据
(2)查询学生

实现两种查询方式:查询所有和按学号查询:

void queryAllStudents() {
  print("\n===== 所有学生 =====");
  if (students.isEmpty) {
    print("暂无学生信息!");
    return;
  }
  for (int i = 0; i < students.length; i++) {
    print("${i + 1}. ${students[i]}"); // 带序号打印
  }
}

void queryStudentById() {
  print("\n===== 按学号查询 =====");
  print("请输入要查询的学号:");
  String id = stdin.readLineSync()?.trim() ?? "";

  // 查找学生(where返回满足条件的可迭代对象)
  Student? student = students.where((s) => s.id == id).firstOrNull;

  if (student != null) {
    print("查询结果:$student");
  } else {
    print("未找到学号为 $id 的学生!");
  }
}
  • 用 where() 结合 firstOrNull 快速查找学生
  • 空列表判断提升用户体验
(3)修改学生信息
void updateStudent() {
  print("\n===== 修改学生 =====");
  print("请输入要修改的学生学号:");
  String id = stdin.readLineSync()?.trim() ?? "";

  Student? student = students.where((s) => s.id == id).firstOrNull;
  if (student == null) {
    print("未找到学号为 $id 的学生!");
    return;
  }

  print("当前信息:$student");

  // 修改姓名(可选,回车跳过)
  print("请输入新姓名(回车不修改):");
  String? newName = stdin.readLineSync()?.trim();
  if (newName != null && newName.isNotEmpty) {
    student.name = newName;
  }

  // 修改年龄(可选,回车跳过)
  print("请输入新年龄(回车不修改):");
  String? ageInput = stdin.readLineSync()?.trim();
  if (ageInput != null && ageInput.isNotEmpty) {
    int newAge = int.tryParse(ageInput) ?? -1;
    if (newAge > 0) {
      student.age = newAge;
    } else {
      print("年龄输入无效,未修改!");
    }
  }

  print("修改成功!更新后信息:$student");
}
  • 支持部分修改(回车跳过),更灵活
  • 对无效输入进行容错处理
(4)删除学生
void deleteStudent() {
  print("\n===== 删除学生 =====");
  print("请输入要删除的学生学号:");
  String id = stdin.readLineSync()?.trim() ?? "";

  Student? student = students.where((s) => s.id == id).firstOrNull;
  if (student == null) {
    print("未找到学号为 $id 的学生!");
    return;
  }

  // 二次确认
  print("确定要删除以下学生吗?[y/n]");
  print(student);
  String? confirm = stdin.readLineSync()?.trim()?.toLowerCase();
  if (confirm == "y" || confirm == "yes") {
    students.remove(student);
    print("删除成功!");
  } else {
    print("已取消删除!");
  }
}
  • 二次确认避免误操作,提升安全性
  • 用 remove() 从列表中删除元素

4. 步骤 4:实现主菜单和程序入口

最后,我们需要一个主循环展示菜单,接收用户选择并调用对应功能:

import 'dart:io'; // 导入输入输出库

void main() {
  print("===== 学生信息管理系统 =====");

  // 主循环(程序入口)
  while (true) {
    print("\n请选择操作:");
    print("1. 添加学生");
    print("2. 查看所有学生");
    print("3. 按学号查询");
    print("4. 修改学生信息");
    print("5. 删除学生");
    print("0. 退出系统");

    String? choice = stdin.readLineSync()?.trim();
    switch (choice) {
      case "1":
        addStudent();
        break;
      case "2":
        queryAllStudents();
        break;
      case "3":
        queryStudentById();
        break;
      case "4":
        updateStudent();
        break;
      case "5":
        deleteStudent();
        break;
      case "0":
        print("感谢使用,再见!");
        return; // 退出程序
      default:
        print("无效选择,请输入0-5之间的数字!");
    }
  }
}
  • 用 while (true) 实现无限循环,直到用户选择退出
  • switch-case 处理菜单选择,逻辑清晰


四、代码优化:从 “能跑” 到 “易读”

上面的代码已经能实现基本功能,但我们可以从以下几个方面优化,让代码更易维护:

1. 优化 1:提取常量和工具类

将重复使用的提示文字、菜单选项等提取为常量,避免硬编码:

// 常量类(存储固定文本)
class Constants {
  static const String appTitle = "===== 学生信息管理系统 =====";
  static const String menuTips = "\n请选择操作:";
  static const List<String> menuOptions = [
    "1. 添加学生",
    "2. 查看所有学生",
    "3. 按学号查询",
    "4. 修改学生信息",
    "5. 删除学生",
    "0. 退出系统",
  ];
  // ... 其他常量
}

// 工具类(封装输入输出逻辑)
class InputUtils {
  // 安全读取输入(处理null和空字符串)
  static String readLine({String tip = ""}) {
    if (tip.isNotEmpty) print(tip);
    return stdin.readLineSync()?.trim() ?? "";
  }

  // 读取整数(带验证)
  static int? readInt({String tip = "", int min = 0}) {
    String input = readLine(tip: tip);
    int? value = int.tryParse(input);
    return (value != null && value >= min) ? value : null;
  }
}

2. 优化 2:拆分复杂函数

将 addStudent 中冗长的输入逻辑拆分为独立函数,比如:

// 从输入获取合法学号
String getValidId() {
  while (true) {
    String id = InputUtils.readLine(tip: "请输入学号:");
    if (id.isEmpty) {
      print("学号不能为空!");
    } else if (isIdExists(id)) {
      print("该学号已存在,请重新输入!");
    } else {
      return id;
    }
  }
}

// 调用时简化为:
String id = getValidId();

3. 优化 3:增加异常处理

用 try-catch 捕获可能的异常(如输入转换错误):

void updateStudent() {
  try {
    // 原有逻辑...
  } catch (e) {
    print("操作失败:${e.toString()}");
  }
}

4. 优化 4:完善注释和文档

为类、函数添加注释,说明功能、参数和返回值:

/// 学生信息实体类
/// 存储学生的学号、姓名和年龄
class Student {
  final String id; // 学号(唯一,不可修改)
  String name; // 学生姓名
  int age; // 学生年龄(正整数)

  /// 构造函数
  /// [id]:学号(必须唯一)
  /// [name]:姓名(非空)
  /// [age]:年龄(大于0)
  Student({required this.id, required this.name, required this.age})
    : assert(age > 0, "年龄必须大于0"); // 断言验证
}


五、扩展功能:让系统更实用

在基础功能上,我们还可以扩展一些实用功能:

  1. 数据持久化:用文件存储学生信息(下次启动不丢失)
  2. 按姓名查询:支持模糊搜索
  3. 排序功能:按学号或年龄排序
  4. 统计功能:计算平均年龄、学生总数等

以 “按姓名模糊查询” 为例:

void queryStudentByName() {
  print("\n===== 按姓名查询 =====");
  String keyword = InputUtils.readLine(tip: "请输入姓名关键字:");
  if (keyword.isEmpty) {
    print("关键字不能为空!");
    return;
  }

  // 筛选姓名包含关键字的学生(忽略大小写)
  List<Student> results = students
      .where((s) => s.name.toLowerCase().contains(keyword.toLowerCase()))
      .toList();

  if (results.isEmpty) {
    print("未找到包含 '$keyword' 的学生!");
  } else {
    print("查询到 ${results.length} 条结果:");
    results.forEach((s) => print(s));
  }
}