如果在开发的业务中遇到嵌套数据,通常我们会使用字典、元组、集合、列表等内置的类型嵌套组合存储数据,例如保存每个学生课程的成绩,它的属性包含姓名,课程,分数。通过内置的字典和列表嵌套实现了存储学生、课程、分数{‘name’:{‘subject’:[score]}}
这种通过嵌套内置类型存储数据的方式在开发中也是经常使用,但是随着业务逻辑的复杂,内置类型的嵌套的层级就越复杂,操作这个嵌套的对象就会非常的复杂。
这篇文章就是通过组合类来替换上面嵌套内置类型开发模式,简化开发代码的复杂度。
下面通过一个使用嵌套内置类型实现业务逻辑,体验下它随着业务越来越复杂,操作嵌套内置类型数据代码也变得复杂。
在一个考试系统中,实现增加学生信息,添加学生所有考试分数,计算学生的平均分。
# 创建一个计算学生平均分的类
class SimpleGradebook:
def __init__(self):
# 初始化一个字典存放学生信息和分数
self._grades = {}
# 创建学生对象
def add_student(self, name):
self._grades[name] = []
# 添加学生分数
def report_grade(self, name, score):
self._grades[name].append(score)
# 计算平均分数
def average_grade(self, name):
grades = self._grades[name]
return sum(grades) / len(grades)
book = SimpleGradebook()
book.add_student('zhansan')
book.report_grade('zhansan', 90)
book.report_grade('zhansan', 95)
book.report_grade('zhansan', 99)
print(book.average_grade('zhansan'))
运行上面的代码,输出了学生的平均分
94.66666666666667
在第一版需求上添加新的需求,将学生的学分按照课程添加,同时在每次考试科目里增加权重,根据科目的权重计算出平均分。
from collections import defaultdict
class WeightGradebook:
def __init__(self):
self._grades = {}
# defaultdict返回一个字典,_grades字典就是一个内置类型的嵌套结构:{'name':{'subject':[(score, weight)]}}
def add_student(self, name):
self._grades[name] = defaultdict(list)
def report_grades(self, name, subject, score, weight):
# 根据学生名称获取所有的课程
bysubject = self._grades[name]
# 获取某一个课程的分数和权重
grade_list = bysubject[subject]
# 添加分数和权重
grade_list.append((score, weight))
# 嵌套内置类型会使业务逻辑变得复杂
def average_grade(self, name):
by_subject = self._grades[name]
score_sum, score_count = 0, 0
for subject, scores in by_subject.items():
subject_avg, total_wight = 0, 0
for score, weight in scores:
subject_avg += score * weight
total_wight += weight
score_sum += subject_avg / total_wight
score_count += 1
return score_sum / score_count
book = WeightGradebook()
book.add_student('zhangsan')
book.report_grades('zhangsan', 'Math', 75, 0.05)
book.report_grades('zhangsan', 'Math', 85, 0.09)
book.report_grades('zhangsan', 'Math', 60, 3.05)
print(book.average_grade('zhangsan'))
使用嵌套类重构代码,它的设计思想归结到一点,就是用类对象替代内置类型存储数据。
嵌套类重构开发思路:
先从依赖书的最底层开始,考虑怎么存储某科目的单次考试成绩与权重。由于分数不需要修改,因此可以使用元组记录考试成绩和权重。
下面来看一个例子
grades = []
grades.append((98, 0.78))
grades.append((88, 0.12))
total = sum(score * weight for score, weight in grades)
total_weight = sum(weight for _, weight in grades)
average_grade = total / total_weight
上面的例子,在计算total_weight时遍历grades元组score不需要因此使用_忽律,并且元组传参使用数字,不容易看出对应的是哪个参数。因此我们可以使用具名元组类代替普通的元组,存储分数和权重。
from collections import namedtuple
Grade = namedtuple('Grade', ('score', 'weight'))
我们的数据中包含了学生对象、课程对象、分数权重对象、其中分数和权重非常简单不需要创建类,用一个数组来存储数据。其余的每个对象都对应的创建一个类存储对象数据。
创建课程类
class Subject:
def __init__(self):
# 创建列表存储课程分数和权重
self._grades = []
# 添加分数和权重
def report_grade(self, score, weight):
self._grades.append(Grade(score, weight))
# 计算平均分
def average_grade(self):
total, total_weight = 0, 0
for grade in self._grades:
total += grade.score * grade.weight
total_weight += grade.weight
return total / total_weight
创建学生类
class Student:
def __init__(self):
# 组合类,将课程类对象组合到学生类中,创建一个Subject类型的字典。
self._subjects = defaultdict(Subject)
# 获取课程
def get_subject(self, name):
return self._subjects[name]
# 计算平均分
def average_grade(self):
total, count = 0, 0
for subject in self._subjects.values():
total += subject.average_grade()
count += 1
return total / count
创建成绩册类
将学生名称和学生对象关联
class Gradebook:
def __init__(self):
self._students = defaultdict(Student)
def get_student(self, name):
return self._students[name]
调用重构的组合类代码
book = Gradebook()
albert = book.get_student('zhangsan')
math = albert.get_subject('Math')
math.report_grade(75, 0.01)
math.report_grade(89, 0.5)
gym = albert.get_subject('Gym')
gym.report_grade(89, 1.9)
gym.report_grade(79, 0.3)
print(albert.average_grade())
输出结果
88.18092691622104