Today, we'll integrate this knowledge through a practical Student Information Management System case, experiencing the complete process from requirement analysis to code implementation, and finally to optimization and refactoring.
I. Requirement Analysis: What Are We Building?
The core requirements of a Student Information Management System are to implement CRUD operations for student information:
- Create: Enter student information like ID, name, and age
- Read: View individual student information or a list of all students
- Update: Modify student information such as name and age
- Delete: Remove information of a specified student
- Exit: Terminate the program
We'll implement these functions with a Dart console application, focusing on class design, collection operations, and function encapsulation.
II. Core Technical Points
Before starting coding, let's clarify the technologies we'll use:
- Class: Define a Student class to encapsulate student information
- List Collection: Use List to store all student data
- Function Encapsulation: Split CRUD functions into independent functions
- User Interaction: Communicate with users through console input/output
- Flow Control: Implement menu logic with loops and branches
III. Code Implementation: Building the System from Scratch
1. Step 1: Define the Student Class (Data Model)
First, we need a Student class to standardize the data structure of student information, containing three core attributes: ID (unique identifier), name, and age:
class Student {
final String id; // Student ID (immutable, unique identifier)
String name; // Name
int age; // Age
// Constructor
Student({required this.id, required this.name, required this.age});
// Override toString method for easy information display
@override
String toString() {
return "ID: $id, Name: $name, Age: $age";
}
}
- id is set as final to ensure it can't be modified, preventing duplicates or accidental changes
- Overriding toString() makes it easier to display student information clearly when printing
2. Step 2: Initialize Data and Utility Functions
Next, we need a list to store students and some utility functions (like checking if an ID exists):
// List to store all students
List<Student> students = [];
// Check if student ID already exists (to prevent duplicates)
bool isIdExists(String id) {
return students.any((student) => student.id == id);
}
3. Step 3: Implement Core CRUD Functions
Encapsulate each operation as an independent function to ensure single responsibility:
(1) Add Student
void addStudent() {
print("\n===== Add Student =====");
// Input student ID (ensure uniqueness)
String id;
while (true) {
print("Please enter student ID:");
id = stdin.readLineSync()?.trim() ?? "";
if (id.isEmpty) {
print("Student ID cannot be empty!");
} else if (isIdExists(id)) {
print("This ID already exists, please try again!");
} else {
break; // ID is valid, exit loop
}
}
// Input name
String name;
while (true) {
print("Please enter name:");
name = stdin.readLineSync()?.trim() ?? "";
if (name.isEmpty) {
print("Name cannot be empty!");
} else {
break;
}
}
// Input age (ensure it's a number)
int age;
while (true) {
print("Please enter age:");
String? ageInput = stdin.readLineSync()?.trim();
age = int.tryParse(ageInput ?? "") ?? -1;
if (age <= 0) {
print("Please enter a valid age!");
} else {
break;
}
}
// Add to list
students.add(Student(id: id, name: name, age: age));
print("Added successfully! Student information: ${students.last}");
}
- Validate input through loops (non-empty, unique ID, positive age)
- Input validation effectively prevents dirty data
(2) Query Students
Implement two query methods: query all students and query by ID:
void queryAllStudents() {
print("\n===== All Students =====");
if (students.isEmpty) {
print("No student information available!");
return;
}
for (int i = 0; i < students.length; i++) {
print("${i + 1}. ${students[i]}"); // Print with serial number
}
}
void queryStudentById() {
print("\n===== Query by ID =====");
print("Please enter the student ID to query:");
String id = stdin.readLineSync()?.trim() ?? "";
// Find student (where returns an iterable of matching items)
Student? student = students.where((s) => s.id == id).firstOrNull;
if (student != null) {
print("Query result: $student");
} else {
print("No student found with ID $id!");
}
}
- Use where() with firstOrNull to quickly find students
- Empty list check improves user experience
(3) Update Student Information
void updateStudent() {
print("\n===== Update Student =====");
print("Please enter the ID of the student to update:");
String id = stdin.readLineSync()?.trim() ?? "";
Student? student = students.where((s) => s.id == id).firstOrNull;
if (student == null) {
print("No student found with ID $id!");
return;
}
print("Current information: $student");
// Update name (optional, press enter to skip)
print("Please enter new name (press enter to keep current):");
String? newName = stdin.readLineSync()?.trim();
if (newName != null && newName.isNotEmpty) {
student.name = newName;
}
// Update age (optional, press enter to skip)
print("Please enter new age (press enter to keep current):");
String? ageInput = stdin.readLineSync()?.trim();
if (ageInput != null && ageInput.isNotEmpty) {
int newAge = int.tryParse(ageInput) ?? -1;
if (newAge > 0) {
student.age = newAge;
} else {
print("Invalid age input, no changes made!");
}
}
print("Update successful! Updated information: $student");
}
- Supports partial updates (press enter to skip), making it more flexible
- Handles invalid inputs with error tolerance
(4) Delete Student
void deleteStudent() {
print("\n===== Delete Student =====");
print("Please enter the ID of the student to delete:");
String id = stdin.readLineSync()?.trim() ?? "";
Student? student = students.where((s) => s.id == id).firstOrNull;
if (student == null) {
print("No student found with ID $id!");
return;
}
// Confirmation
print("Are you sure you want to delete the following student? [y/n]");
print(student);
String? confirm = stdin.readLineSync()?.trim()?.toLowerCase();
if (confirm == "y" || confirm == "yes") {
students.remove(student);
print("Deletion successful!");
} else {
print("Deletion cancelled!");
}
}
- Double confirmation prevents accidental operations, improving safety
- Use remove() to delete elements from the list
4. Step 4: Implement Main Menu and Program Entry
Finally, we need a main loop to display the menu, receive user choices, and call corresponding functions:
import 'dart:io'; // Import input/output library
void main() {
print("===== Student Information Management System =====");
// Main loop (program entry)
while (true) {
print("\nPlease select an operation:");
print("1. Add Student");
print("2. View All Students");
print("3. Query by ID");
print("4. Update Student");
print("5. Delete Student");
print("0. Exit System");
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("Thank you for using, goodbye!");
return; // Exit program
default:
print("Invalid choice, please enter a number between 0-5!");
}
}
}
- Use while (true) to create an infinite loop until the user chooses to exit
- switch-case handles menu selection with clear logic
IV. Code Optimization: From "Working" to "Readable"
The code above already implements basic functionality, but we can optimize it in several ways to make it more maintainable:
1. Optimization 1: Extract Constants and Utility Classes
Extract frequently used prompt texts and menu options as constants to avoid hardcoding:
// Constant class (stores fixed text)
class Constants {
static const String appTitle =
"===== Student Information Management System =====";
static const String menuTips = "\nPlease select an operation:";
static const List<String> menuOptions = [
"1. Add Student",
"2. View All Students",
"3. Query by ID",
"4. Update Student",
"5. Delete Student",
"0. Exit System",
];
// ... other constants
}
// Utility class (encapsulates input/output logic)
class InputUtils {
// Safely read input (handle null and empty strings)
static String readLine({String tip = ""}) {
if (tip.isNotEmpty) print(tip);
return stdin.readLineSync()?.trim() ?? "";
}
// Read integer (with validation)
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. Optimization 2: Split Complex Functions
Split the lengthy input logic in addStudent into independent functions:
// Get valid ID from input
String getValidId() {
while (true) {
String id = InputUtils.readLine(tip: "Please enter student ID:");
if (id.isEmpty) {
print("Student ID cannot be empty!");
} else if (isIdExists(id)) {
print("This ID already exists, please try again!");
} else {
return id;
}
}
}
// Simplified call:
String id = getValidId();
3. Optimization 3: Add Exception Handling
Use try-catch to catch potential exceptions (like input conversion errors):
void updateStudent() {
try {
// Original logic...
} catch (e) {
print("Operation failed: ${e.toString()}");
}
}
4. Optimization 4: Improve Comments and Documentation
Add comments to classes and functions explaining their purpose, parameters, and return values:
/// Student student information entity class
/// Stores stores student ID, name, and age
class Student {
final String id; // Student ID (unique, unmodifiable)
String name; // Student name
int age; // Student age (positive integer)
/// Constructor
/// [id]: Student ID (must be unique)
/// [name]: Student name (non-empty)
/// [age]: Student age (greater than 0)
Student({required this.id, required this.name, required this.age})
: assert(age > 0, "Age must be greater than 0"); // Assertion validation
}
V. Extended Features: Making the System More Practical
We can extend the basic functionality with some useful features:
- Data Persistence: Store student information in files (persists between launches)
- Name-based Query: Support fuzzy search
- Sorting: Sort by ID or age
- Statistics: Calculate average age, total number of students, etc.
Take "fuzzy query by name" as an example:
void queryStudentByName() {
print("\n===== Query by Name =====");
String keyword = InputUtils.readLine(tip: "Please enter name keyword:");
if (keyword.isEmpty) {
print("Keyword cannot be empty!");
return;
}
// Filter students whose names contain the keyword (case-insensitive)
List<Student> results = students
.where((s) => s.name.toLowerCase().contains(keyword.toLowerCase()))
.toList();
if (results.isEmpty) {
print("No students found containing '$keyword'!");
} else {
print("Found ${results.length} results:");
results.forEach((s) => print(s));
}
}
Top comments (0)