Skip to content

案例:学生信息管理系统的面向对象封装 ​

前面我们已经学习了类、实例对象、类属性和实例方法。

这一节继续改造函数化学生信息管理系统:把之前的过程式写法改成面向对象写法,让学生数据、数据管理和交互流程各自负责一部分工作。如何拆分这些职责,才能让每个类只处理自己负责的内容?

需求:在不改变学生信息管理系统主要功能的前提下,把学生数据、数据管理和交互流程拆分为职责清楚的对象。

功能分析:先定位过程式代码的维护问题,再依次建立学生对象、数据管理对象和业务流程对象,最后检查三者如何协作。

过程式结构的局限 ​

之前我们保存学生信息,通常是这样写:

python
student_list = [
    {'name': '张三', 'chinese': 65, 'math': 65, 'english': 65, 'total': 195},
    {'name': '李四', 'chinese': 65, 'math': 65, 'english': 65, 'total': 195},
    {'name': '王五', 'chinese': 65, 'math': 65, 'english': 65, 'total': 195},
]

student = student_list[0]
print(student['name'])

这种写法能用,但问题也很明显:

  1. 字典的键容易写错
  2. 学生数据和操作数据的代码分散在不同位置
  3. 功能增加后,代码会越来越难维护

所以这一步我们要开始拆职责。

过程式字典散装 vs 面向对象模型封装

学生对象模型 ​

第一步,先把一名学生的数据封装成对象。如何让学生的属性、总分计算和数据转换保持在同一个职责边界内?

学生模型最主要的作用,就是在“字典数据”和“对象”之间做转换。

这样后面写业务代码时,就不用一直写:

python
student['name']
student['math']
student['english']

而是直接通过对象属性来访问:

python
student.name
student.math
student.english

对象版写法如下:

python
class Student:
    def __init__(self, name, chinese, math, english):
        self.name = name
        self.chinese = chinese
        self.math = math
        self.english = english

    def total(self):
        return self.chinese + self.math + self.english

    def __str__(self):
        return (
            f'{self.name}:语文 {self.chinese},数学 {self.math},'
            f'英语 {self.english},总分 {self.total()}'
        )

    def to_json(self):
        return {
            'name': self.name,
            'chinese': self.chinese,
            'math': self.math,
            'english': self.english,
        }


data = {
    'name': '正心',
    'chinese': 60,
    'math': 60,
    'english': 60,
}
zx = Student(**data)
print(zx)
print(zx.to_json())

这一层先记住四件事:

  1. Student 负责保存一名学生的姓名和三科成绩
  2. total() 负责计算总分
  3. __str__() 负责生成便于阅读的学生信息
  4. to_json() 负责把对象转回字典,方便后续保存

学生数据在字典、对象和 JSON 之间转换

数据管理对象 ​

第二步,把“数据怎么保存、怎么读取、怎么增删改查”单独封装起来。学生对象建立后,谁来负责本地数据的读写和增删改查?

这部分不直接接收用户输入,也不负责输出操作提示。

它只负责一件事:

管理学生数据。

这里把它封装到 Database 类里。为了方便观察三个对象怎样配合,完整示例暂时写在同一个文件中:

python
import json


class Student:
    def __init__(self, name, chinese, math, english):
        self.name = name
        self.chinese = chinese
        self.math = math
        self.english = english

    def total(self):
        return self.chinese + self.math + self.english

    def __str__(self):
        return (
            f'{self.name}:语文 {self.chinese},数学 {self.math},'
            f'英语 {self.english},总分 {self.total()}'
        )

    def to_json(self):
        return {
            'name': self.name,
            'chinese': self.chinese,
            'math': self.math,
            'english': self.english,
        }


class Database:
    def __init__(self, filename=None):
        self.filename = filename or 'student.json'
        self.student_list = []
        self._load()

    def _load(self):
        """将本地数据加载为对象"""
        with open(self.filename, mode='r', encoding='utf-8') as file:
            data = file.read()
            student_list = json.loads(data)
            for student in student_list:
                self.student_list.append(Student(**student))

    def save(self):
        """将数据保存到本地"""
        with open(self.filename, mode='w', encoding='utf-8') as file:
            student_list = []
            for student in self.student_list:
                student_list.append(student.to_json())
            data = json.dumps(student_list, ensure_ascii=False, indent=2)
            file.write(data)

    def add(self, student):
        self.student_list.append(Student(**student))

    def change(self, old_name, student):
        stu = self.search_by_name(old_name)
        stu.name = student['name']
        stu.math = student['math']
        stu.chinese = student['chinese']
        stu.english = student['english']

    def delete(self, name):
        stu = self.search_by_name(name)
        self.student_list.remove(stu)

    def search_by_name(self, name):
        for student in self.student_list:
            if student.name == name:
                return student
        return None

    def show_all(self):
        print('姓名\t\t语文\t\t数学\t\t英语\t\t总分')
        for stu in self.student_list:
            print("{}\t\t{}\t\t{}\t\t{}\t\t{}".format(stu.name, stu.chinese, stu.math, stu.english, stu.total()))


class StudentManager:
    def __init__(self):
        self.db = Database()
        self.str_info = """**************************************************
欢迎使用【学生信息管理系统】V1.0
请选择你想要进行的操作
1. 新建学生信息
2. 显示全部信息
3. 查询学生信息
4. 修改学生信息
5. 删除学生信息

0. 退出系统
**************************************************"""

    def add_student(self):
        name = input('请输入学生的姓名:')
        chinese = int(input('请输入学生的语文成绩:'))
        math = int(input('请输入学生的数学成绩:'))
        english = int(input('请输入学生的英语成绩:'))
        stu = Student(name=name, chinese=chinese, math=math, english=english)
        self.db.add(stu.to_json())

    def change_student(self):
        name = input('请输入你想要修改的学生姓名:')
        stu = self.db.search_by_name(name)
        if stu:
            name = input('请重新输入学生的名字:')
            chinese = int(input('请重新输入学生的语文成绩:'))
            math = int(input('请重新输入学生的数学成绩:'))
            english = int(input('请重新输入学生的英语成绩:'))
            student = {'name': name, 'chinese': chinese, 'math': math, 'english': english}
            self.db.change(stu.name, student)
        else:
            print(f'{name} 不存在')

    def run(self):
        while True:
            print(self.str_info)
            action = input('请选择你想要进行的操作:')
            if action == '1':
                self.add_student()
            elif action == '2':
                self.db.show_all()
            elif action == '3':
                name = input('请输入你想要查询的学生姓名:')
                stu = self.db.search_by_name(name)
                print(stu)
            elif action == '4':
                self.change_student()
            elif action == '5':
                name = input('请输入你想要删除的学生姓名:')
                stu = self.db.search_by_name(name)
                if stu:
                    self.db.delete(name)
                else:
                    print(f'{name} 不存在')
            elif action == '0':
                self.db.save()
                print('0. 退出系统')
                break
            else:
                print('请输入正确的选择')


if __name__ == '__main__':
    student_manager = StudentManager()
    student_manager.run()

运行示例前,在 db.py 同级目录中新建 student.json,初始内容是一个空列表:

json
[]

在两个文件所在目录运行,Bash 和 PowerShell 使用相同命令:

shell
python3 db.py

这一层的核心作用是:

  1. 加载本地数据
  2. 保存本地数据
  3. 新增、修改、删除、查询学生

也就是说,数据相关的操作都集中到一起了。

业务流程对象 ​

第三步,组织具体的业务流程。数据已经有了对象和管理者,谁来负责接收用户操作并推动整个系统运行?

比如:

  • 用户输入什么
  • 调用哪个功能
  • 查不到数据时提示什么
  • 退出时什么时候保存

这部分由示例代码中的 StudentManager 负责。

它不直接管理学生列表,而是调用 Database 完成数据操作。

可以这样理解三层职责:

  1. Student:负责学生对象本身
  2. Database:负责学生数据管理
  3. StudentManager:负责系统交互和业务流程

Student、Database 和 StudentManager 的三层职责

职责拆分结果 ​

拆分之后,每个类的职责会更清楚,后续修改可以先定位到对应对象:

比如:

  • 想改学生结构,优先看 Student
  • 想改数据保存方式,优先看 Database
  • 想改交互流程,优先看 StudentManager

这就是面向对象写法的价值之一:把不同职责拆开,不让所有代码混在一起。

StudentManager 到 Database 再到 Student 的调用链

小结 ​

这一节重点不是再讲一个新语法点,而是把前面的对象知识真正用起来。

本节最核心的是三件事:

  1. 用 Student 表示学生对象
  2. 用 Database 统一管理数据
  3. 用 StudentManager 组织业务流程

练习 ​

  1. 给 Student 增加一个 phone 属性,并同步修改 to_json()
  2. 修改 Database.show_all():没有学生数据时,输出“暂无学生信息”
  3. 在 StudentManager 中增加删除确认,输入 y 后才真正删除学生