角色: 学校、学员、课程、讲师、管理员 要求: 1. 建立北京、上海 2 所学校 ---> 管理员建立学校 2. 建立linux , python , go 3个课程 , linux\py 在北京开, go 在上海开 3. 课程包含,周期,价格,经过学校建立课程 4. 建立讲师 5. 建立学员时,选择学校,关联班级 5. 建立讲师 6. 提供两个角色接口 6.1 学员视图, 直接登陆,选择课程(等同于选择班级) 6.2 讲师视图, 讲师可管理本身的课程, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩 6.3 管理视图,建立讲师, 建立班级,建立课程等 7. 上面的操做产生的数据都经过pickle序列化保存到文件里 - pickle 能够帮咱们保存对象
角色设计:管理员、学校、老师、学生、课程等 需求分析 (课程与班级合为一体) - 管理员视图 - 1.注册 - 2.登陆 - 3.建立学校 - 4.建立课程(先选择学校) - 5.建立讲师(默认设置初始密码) - 6.建立学生(先选择学校,默认设置初始密码) - 7.修改密码 - 8.重置老师、学生密码 - 学员视图 - 1.登陆功能 - 2.选择课程 - 3.已选课程查看 - 5.查看分数 - 6.修改密码 - 讲师视图 - 1.登陆 - 2.查看课程 - 3.选择课程 - 4.个人学生(按课程分类查看) - 5.修改学生分数(找到课程再找学生) - 6.修改密码
实现思路:css
项目采用三层架构设计,基于面向对象封装角色数据和功能。面向过程和面向对象搭配使用。python
程序开始,用户选择角色,进入不一样的视图层,展现每一个角色的功能,供用户选择。linux
进入具体角色视图后,调用功能,对接逻辑接口层获取数据并展现给用户视图层。git
逻辑接口层须要调用数据处理层的类,获取类实例化对象,进而实现数据的增删改查。github
# 用户视图层 - 提供用户数据交互和展现的功能 # 逻辑接口层 - 提供核心逻辑判断,处理用户的请求,调用数据处理层获取数据并将结果返回给用户视图层 # 数据处理层 - 提供数据支撑,使用面向对象的数据管理,将数据和部分功能封装在类中,将对象保存在数据库
程序结构:数据库
CSS/ # Course Selection System |-- conf | |-- setting.py # 项目配置文件 |-- core | |-- admin.py # 管理员视图层函数 | |-- current_user.py # 记录当前登陆用户信息 | |-- teacher.py # 老师视图层函数 | |-- student.py # 学生视图层函数 | |-- css.py # 主程序(作视图分发) |-- db |-- |-- models.py # 存放类 | |-- db_handle.py # 数据查询和保存函数 | |-- Admin # 管理员用户对象文件夹 | |-- Course # 课程对象文件夹 | |-- School # 学校对象文件夹 | |-- Student # 学生对象文件夹 | |-- Teacher # 老师对象文件夹 |-- interface # 逻辑接口 | |-- admin_interface.py # 管理员逻辑接口 | |-- common_interface.py # 公共功能逻辑接口 | |-- student_interface.py # 学生功能逻辑接口 | |-- teacher_interface.py # 老师功能逻辑接口 |-- lib | |-- tools.py # 公用函数:加密|登陆装饰器权限校验等 |-- readme.md |-- run.py # 项目启动文件
版本:windows
版本1:采用上述的逻辑架构,视图层采层面向过程的方式,即函数组织。数组
版本2:用户视图层采用面向对象的封装加反射,实现用户功能函数的自动添加(但我的感受不如面向过程的简洁清晰)。架构
项目源码在github我的仓库,感兴趣的园友能够参考,欢迎交流分享。点击一下链接到仓库地址app
下面默认总结版本1的要点,总结版本2的要点时会明显指出(即用类封装视图层的两个关键点:装饰器,Mixins)。
- windows10, 64位 - python3.8 - pycharm2019.3
import sys from conf import settings from db import db_handle class FileMixin: @classmethod def get_obj(cls, name): return db_handle.get_obj(cls, name) def save_obj(self): db_handle.save_obj(self) class Human: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex self.__pwd = settings.INIT_PWD self.role = self.__class__.__name__ @property def pwd(self): return self.__pwd @pwd.setter def pwd(self, new_pwd): self.__pwd = new_pwd class Admin(FileMixin, Human): def __init__(self, name, age, sex): super().__init__(name, age, sex) self.save_obj() @staticmethod def create_school(school_name, school_addr): School(school_name, school_addr) @staticmethod def create_course(school_name, course_name, course_period, course_price): Course(course_name, course_period, course_price, school_name) @staticmethod def create_teacher(teacher_name, teacher_age, teacher_sex, teacher_level): Teacher(teacher_name, teacher_age, teacher_sex, teacher_level) @staticmethod def create_student(stu_name, stu_age, stu_sex, school_name, homeland): Student(stu_name, stu_age, stu_sex, school_name, homeland) @staticmethod def reset_user_pwd(name, role): obj = getattr(sys.modules[__name__], role).get_obj(name) obj.pwd = settings.INIT_PWD obj.save_obj() class School(FileMixin): def __init__(self, name, addr): self.name = name self.addr = addr self.course_list = [] self.save_obj() def relate_course(self, course_name): self.course_list.append(course_name) self.save_obj() class Course(FileMixin): def __init__(self, name, period, price, school_name): self.name = name self.period = period self.price = price self.school = school_name self.teacher = None self.student_list = [] self.save_obj() def relate_teacher(self, teacher_name): self.teacher = teacher_name self.save_obj() def relate_student(self, stu_name): self.student_list.append(stu_name) self.save_obj() class Teacher(FileMixin, Human): def __init__(self, name, age, sex, level): super().__init__(name, age, sex) self.level = level self.course_list = [] self.save_obj() def select_course(self, course_name): self.course_list.append(course_name) self.save_obj() course_obj = Course.get_obj(course_name) course_obj.relate_teacher(self.name) def check_my_courses(self): return self.course_list @staticmethod def check_my_student(course_name): course_obj = Course.get_obj(course_name) return course_obj.student_list @staticmethod def set_score(stu_name, course_name, score): stu_obj = Student.get_obj(stu_name) stu_obj.score_dict[course_name] = int(score) stu_obj.save_obj() class Student(FileMixin, Human): def __init__(self, name, age, sex, school_name, homeland): super().__init__(name, age, sex) self.school = school_name self.homeland = homeland self.course_list = [] self.score_dict = {} self.save_obj() def select_course(self, course_name): self.course_list.append(course_name) self.score_dict[course_name] = None self.save_obj() course_obj = Course.get_obj(course_name) course_obj.relate_student(self.name) def check_my_course(self): return self.course_list def check_my_score(self): return self.score_dict
从管理员、学生、老师角色中抽象出Human
类,有用户基本数据属性和密码相关的公共属性
为了角色数据的读取和保存,定义了一个接口类FileMixin
,用于对象数据的读取和保存。
FileMixin
中设置一个绑定类的方法,这样每一个继承FileMixin
的类均可以经过对象名判断这个对象的存在与否。
注意,多继承时遵循Mixins
规范。
对象初始化后当即保存数据,每一个功能操做后,也跟一个save_obj
方法,这样类的使用者就很方便。
在用户类中设置角色的方法属性,这样直接在逻辑接口层中在获取对象后,直接调用对象的方法便可。这样作是为了保证面向对象的完整性,每一个对象都对应其现实意义。
每一个角色都有登陆需求,所以这里打算作一个公用的登陆逻辑接口层。
不过由于数据存放格式的限制,这里妥协一下。每一个登陆视图层仍是直接调用各自的登陆逻辑接口,而后从各自的逻辑接口层中调用公用逻辑接口层的核心登陆逻辑判断。
这里在角色的登陆接口中作一个中转的目的是为了给登陆用户设置一个登陆角色;
而且这个角色的字符串名字和类的名字保持一致,为了方便在公共登陆接口中使用反射判断。
admin_interface.py
def login_interface(name, pwd): """ 登陆接口 :param name: :param pwd: 密码,密文 :return: """ from interface import common_interface role = 'Admin' flag, msg = common_interface.common_login_interface(name, pwd, role) return flag, msg
common_interface.py
def common_login_interface(name, pwd, role): """ 登陆接口 :param name: :param pwd: 密码,密文 :param role: 角色,如,Admin|Teacher|Student :return: """ if hasattr(models, role): obj = getattr(models, role).get_obj(name) if not obj: return False, f'用户名[{name}]不存在' if pwd != obj.pwd: return False, '用户名或密码错误' return True, '登陆成功' else: return False, '您没有权限登陆'
这个项目按照三层架构的模式,只要实现了一个角色,其余角色的功能在编写的时候,会存在大量重复的代码。
因此,尽量地提取公共的逻辑接口和工具函数,减轻程序组织结构臃肿,提升代码复用率。
场景一:视图层中,功能函数的展现和选择
这个场景主要用在视图分发和视图内用户功能函数的选择。
若是视图层采用面向对象的方式,封装成一个视图类,使用装饰器和反射就能够避免功能字典的使用。
lib/tools.py
def menu_display(menu_dict): """ 展现功能字典,然用户选择使用 :param menu_dict: :return: """ while 1: for k, v in menu_dict.items(): print(f'({k}) {v[0]}', end='\t') func_choice = input('\n请输入选择的功能编号(Q退出):').strip().lower() if func_choice == 'q': break if func_choice not in menu_dict: continue func = menu_dict.get(func_choice)[1] func()
场景二:展现数据并返回用户选择的数据
这个场景是用户在选择一个需求时,先将选项展现给用户看,供用户输入选择编号。
这个过程就涉及到用户的退出选择和输入编号的合法性验证。返回用户的选择结果或者错误信息提示。
前提:调用该函数以前判断info_list
为空的状况;在该函数内也能够判断,不一样这样的话就下降了其通用程度。
lib/tools.py
def select_item(info_list): """ 枚举展现数据列表,并支持用户数据编号返回编号对应的数据,支持编号合法校验 :param info_list: :return: """ while 1: for index, school in enumerate(info_list, 1): print(index, school) choice = input('请输入选择的编号(Q退出):').strip().lower() if choice == 'q': return False, '返回' if not choice.isdigit() or int(choice) not in range(1, len(info_list) + 1): print('您输入的编号不存在') continue else: return True, info_list[int(choice) - 1]
这样的需求或者说场景还有不少,不作列举。
将一个类实例化对象按照类型保存在不一样的文件夹中,文件夹名与类名相同,文件名为对象的name属性的名字。
这样作的好处是方便对象数据的读取和保存,而且对象间没有使用组合的方式,避免数据的重复保存。
可是这样作的缺点很明显:每一个类下面的对象不能重名。这个问题须要从新组织数据管理方式,让其更实际化。
之因此想要将视图层封装成视图类,主要是为了简化代码和避免手动编写用户的功能函数字典。
采用视图类以后,能够将功能函数作成视图类的对象的绑定方法,采用反射,能够自动获取并调用。
但这里须要作一个处理:用户选择角色后,如何获取并显示这个角色的功能函数函数列表?
这里须要在视图类里面作一个显示功能的方法start
,这个方法要在用户选择先显示全部的功能,
在此以前,还须要一个收集角色功能的方法auto_get_func_menu
,这个函数必须在对象使用时就当即工做,
最后,还要配合一个装饰器my_func
,让收集函数知道搜集那些功能,保存下来func_list
,让显示函数获取。
上述这个过程涉及的方法是每一个视图类都要有的,所以抽象出来一个基础视图类BaseViewer
。
最后,视图类须要用到一些公用工具(lib/tool.py),将它封装成一个ToolsMixin
类,视图类继承之,方便传参。
关键点:
core/baseview.py
from functools import wraps class BaseViewer: name = None role = None func_list = [] # 存放角色功能方法 def __init__(self): self.auto_get_func_menu() # 初始化就启动,搜集角色功能方法 def auto_get_func_menu(self): """ 自动调用功能函数触发装饰器的执行,将功能函数添加到类属性 func_list中 :return: """ not_this = ['auto_get_func_menu', 'my_func', 'start'] all_funcs = {k: v for k, v in self.__class__.__dict__.items() if callable(v) and not k.startswith('__') and k not in not_this} for func in all_funcs.values(): func() def start(self): """ 开始函数,功能菜单显示,供管理员选择 :return: """ while 1: for index, func_name in enumerate(self.func_list, 1): print('\t\t\t\t\t\t', index, func_name[0], sep='\t') choice = input('>>>(Q退出):').strip().lower() if choice == 'q': self.func_list.clear() break if not choice.isdigit() or int(choice) not in range(1, len(self.func_list) +1): print('编号不存在, 请从新输入') continue func = self.func_list[int(choice) - 1][1] func(self) @staticmethod def my_func(desc): """ 装饰器,实现功能函数自动添加到类的func_list中 :return: """ def wrapper(func): @wraps(func) def inner(*args, **kwargs): BaseViewer.func_list.append((desc, func)) return inner return wrapper @staticmethod def auth(role): """ 装饰器,登陆校验 :return: """ def wrapper(func): @wraps(func) def inner(*args, **kwargs): if BaseViewer.name and BaseViewer.role == role: res = func(*args, **kwargs) return res else: print('您未登陆或没有该功能的使用权限') return inner return wrapper def login(self, role_interface): while 1: print('登陆页面'.center(50, '-')) name = input('请输入用户名(Q退出):').strip().lower() if name == 'q': break pwd = input('请输入密码:').strip() if self.is_none(name, pwd): print('用户名或密码不能为空') continue flag, msg = role_interface.login_interface(name, self.hash_md5(pwd)) print(msg) if flag: BaseViewer.name = name break
学生视图类:core/student.py
from core.baseview import BaseViewer as Base from lib.tools import ToolsMixin from interface import student_interface, common_interface class StudentViewer(ToolsMixin, Base): @Base.my_func('登陆') def login(self): Base.role = 'Student' super().login(student_interface) @Base.my_func('选择课程') @Base.auth('Student') def select_course(self): while 1: school_name = student_interface.get_my_school_interface(self.name) flag, course_list = common_interface.get_course_list_from_school(school_name) if not flag: print(course_list) break print('待选课程列表'.center(30, '-')) flag2, course_name = self.select_item(course_list) if not flag2: break flag3, msg = student_interface.select_course_interface(course_name, self.name) print(msg) @Base.my_func('个人课程') @Base.auth('Student') def check_my_course(self): flag, course_list = student_interface.check_my_course_interface(self.name) if not flag: print(course_list) return print('个人课程:'.center(30, '-')) for index, course_name in enumerate(course_list, 1): print(index, course_name) @Base.my_func('个人分数') @Base.auth('Student') def check_my_score(self): flag, score_dict = student_interface.check_score_interface(self.name) if not flag: print(score_dict) else: print('课程分数列表') for index, course_name in enumerate(score_dict, 1): score = score_dict[course_name] print(index, course_name, score) @Base.my_func('修改密码') @Base.auth('Student') def edit_my_pwd(self): self.edit_pwd(common_interface.edit_pwd_interface)
必定要先分析需求,再构思设计,最后开始编码。
角色设计时,须要考虑角色之间的关系,抽象继承,多继承遵循Mixins
规范。
使用property,遵循鸭子类型,方便接口设计。
基于反射能够作不少动态判断,避免使用if-elif-else
多级判断。
面向过程和面向对象搭配使用。
三层架构,明确每层职责,分别使用面向对象和面向过程编码。
尽量封装成工具:函数或者类