选课系统编写思路
资料下载
项目目的
- 在ATM+购物车项目的所掌握的三层架构、软件开发目录之上,进一步拓展
- 在项目中加入面向对象的封装、继承、多态、反射的要素,训练面向对象的思维
架构设计
在三层架构的基础上,做了一些简单的改变。
-
用户交互层也进行了分叉,不同的用户类型将访问不同的功能
分别是管理员、学生、教师三个视图。
-
无论是什么视图,作为用户都有注册登录功能,实际上这段代码是大同小异的
所以如果会面向对象之反射的话,就可以对每个视图的注册登录功能做统合,当然相应的也会有与接口来处理其与数据交互层的连接。
下文会详细讲解。
-
数据处理采取了对象存储的方法,采取了pickle模块,能将python的对象类型序列化存储到文件里,而对象中存储了对象独有的数据。
-
每种对象数据都有存储功能,我们可以让它继承父类中的存储功能,使用类名和对象进行区别,做到‘分类’存储。
重要环节讲解
统合注册登录界面
auth_view.py 属于用户交互层,不同的用户类型只会登录它相应的登录态,如Admin视窗就只会改变login_user['Admin']的状态,得到一个管理员登录用户
from interface import auth_inf
from conf import settings
from lib import common
# 保存所有用户类型的登录状态
login_user = {
'Admin': '',
'Teacher': '',
'Student': ''
}
def register(mode):
ch_mode = settings.MODE_CH_DICT.get(mode) # 每个类型的有不同的字符
while True:
username = input(f'请输入{ch_mode}用户名(q退出登录):').strip()
if username == 'q':
return False
flag, msg = auth_inf.is_obj_exist_inf(username, mode=mode)
# 根据不同的用户类型,启用的接口也会有区别
if flag:
print(f'{ch_mode}用户【{username}】已被注册')
continue
password = input(f'请输入{ch_mode}密码:').strip()
confirm_pwd = input(f'请确认{ch_mode}密码:').strip()
if not password == confirm_pwd:
print('两次输入的密码不一致')
continue
password = common.get_hash(password)
# if mode == 'Student':
flag, msg = auth_inf.register_inf(username, password, mode=mode)
print(f'{ch_mode}【{username}】注册成功')
break
return True
def login(mode):
ch_mode = settings.MODE_CH_DICT.get(mode)
while True:
username = input(f'请输入{ch_mode}用户名:').strip()
if username == 'q':
return False
flag, msg = auth_inf.is_obj_exist_inf(username, mode=mode)
if not flag:
print(f'不存在{ch_mode}用户【{username}】')
continue
password = input(f'请输入{ch_mode}密码:').strip()
password = common.get_hash(password)
flag, msg = auth_inf.login_inf(username, password, mode=mode)
if flag:
print(f'{ch_mode}用户{username}登录成功')
login_user[mode] = username
return True
print('密码错误')
def login_auth(mode):
def outer(func):
def inner(*args, **kwargs):
name = login_user[mode]
if name:
if auth_inf.is_obj_valid(name, mode):
res = func(*args, **kwargs)
return res
print(f'{name}已被管理员冻结,请联系管理员')
return
print('你还未登录,不能使用这些功能')
login(mode)
return inner
return outer
auth_inf.py 专门转接认证方面的功能,用到了反射的功能,将字符串'Admin'转换为models模块中的类,每个类中都有相应的注册方法,校验密码方法等方法,而且同名,如校验密码都用了verify_pwd,这里就是用了多态的思想,如果大家都是干一件事,就同名,那么我们拿到不同对象时,无论是什么类的对象,都存在一个verify_pwd方法为我们校验密码
from db import models
def is_obj_exist_inf(username, *, mode):
cls = getattr(models, mode)
if cls.select(username):
return True, f'{cls.__name__}对象【{username}】存在'
return False, f'{cls.__name__}对象【{username}】不存在'
def register_inf(username, pwd, *, mode, **kwargs):
cls = getattr(models, mode)
cls(username, pwd, **kwargs)
return True, f'{cls.__name__}【{username}】注册成功'
def login_inf(username, password, *, mode):
cls = getattr(models, mode)
user_obj = cls.select(username)
if user_obj.verify_pwd(password): # 校验密码的多态
return True, '登录成功'
return False, '登录失败'
def is_obj_valid(name, mode):
cls = getattr(models, mode)
if not hasattr(cls.select(name), 'valid'):
return True
return cls.select(name).valid
def modify_pwd(username, new_pwd, *, mode):
cls = getattr(models, mode)
user_obj = cls.select(username)
user_obj.modify_pwd(new_pwd)
return True, '密码修改成功'
models.Admin 数据层的admin类,使用了封装的思想,先将pwd隐藏起来,让用户不能读取,再开放接口进行校验。这个数据属于敏感数据,所以就使用了封装后开放接口的做法,来避免这个数据被滥用。(这里还有很多数据也可以采取这种思想,如名字作为唯一凭证应该也只读不能删改,这里就留给读者去思考)
class Admin(DBHandler):
def __init__(self, name, pwd):
self.name = name
self.__pwd = pwd
self.save()
def verify_pwd(self, password): # 校验密码只能通过这个接口
return password == self.__pwd
def create_school(self, school_name, addr):
School(school_name, addr)
def lock_obj(self, cls, name):
obj = cls.select(name)
obj.valid = False
obj.save()
def unlock_obj(self, cls, name):
obj = cls.select(name)
obj.valid = True
obj.save()
注册登录功能与核心功能的流程优化
student_view.py 的run函数
注册登录应该是单独的界面,让用户成功登录后再给他看所有的核心功能
为此,可以采取两个并列的循环来实现,在登录成功时,使第一循环break,就可以成功进入第二个循环,向用户展示核心功能,让用户可以循环使用
def run():
while True:
if auth_view.login_user[MODE]:
break
choice = input("""
+----学生登录注册界面----+
| 1 注册 |
| 2 登录 |
+---------------------+
请输入编号(q返回首页):""")
if choice == 'q':
return
if choice == '1':
auth_view.register(MODE)
elif choice == '2':
flag = auth_view.login(MODE)
if flag:
print('登录成功')
break
else:
print('输入1或2进入注册或者登录')
continue
student_user = auth_view.login_user[MODE]
while True:
choice = input(f'''
=========您好,学生【{student_user}】=========
1 修改密码
2 绑定学校
3 选择课程
4 查看成绩单
=============<学生首页>=============
请输入功能编号(q返回首页)''').strip()
if choice == 'q':
return
if choice in func_dict:
func_dict[choice]()
else:
print('请输入正确的功能编号')
具体的效果如下:
对象是数据和功能的集合体
models.Teacher.py 上面的这句话怎么理解呢,实际上可以看到关于数据处理的逻辑都是在models的类中完成的,创建课程时,两者一定会互相绑定,让teacher对象的course_list中添加这个课程的名字,调用Course(course_name, price, school, self.name)创建并存储课程时也传入教师的名字,让课程对象保存到它的school属性中
而在接口层只是对这些对象简单的存取和调用对象中的功能罢了。
class Teacher(DBHandler):
def __init__(self, name, pwd):
self.name = name
self.__pwd = pwd
self.valid = True
self.course_list = []
self.save()
def verify_pwd(self, password):
return password == self.__pwd
def modify_pwd(self, new_pwd):
self.__pwd = new_pwd
self.save()
def create_course(self, course_name, price, school):
Course(course_name, price, school, self.name)
self.course_list.append(course_name)
self.save()
def get_course_infos_list(self):
# ['课程名','价格','学校','地址','讲师'] * n
return [Course.select(name).get_course_infos()
for name in self.course_list]
def modify_score(self, course_name, student_name, score):
student_obj = Student.select(student_name)
student_obj.scores_list[course_name] = score
student_obj.save()
接口层 teacher_inf.py
def create_course_inf(teacher_user, school_name, course_name, price):
teacher_obj = models.Teacher.select(teacher_user)
teacher_obj.create_course(course_name, price, school_name)
school_obj = models.School.select(school_name)
school_obj.append_course(course_name)
return True, f'您开设的课程【{course_name}】已在【{school_name}】开设'