• 组合类替换嵌套内置类型实现多层嵌套业务


    组合类替换嵌套内置类型实现多层嵌套业务

    1.概述

    如果在开发的业务中遇到嵌套数据,通常我们会使用字典、元组、集合、列表等内置的类型嵌套组合存储数据,例如保存每个学生课程的成绩,它的属性包含姓名,课程,分数。通过内置的字典和列表嵌套实现了存储学生、课程、分数{‘name’:{‘subject’:[score]}}
    这种通过嵌套内置类型存储数据的方式在开发中也是经常使用,但是随着业务逻辑的复杂,内置类型的嵌套的层级就越复杂,操作这个嵌套的对象就会非常的复杂。
    这篇文章就是通过组合类来替换上面嵌套内置类型开发模式,简化开发代码的复杂度。

    2.使用嵌套内置类型开发业务示例

    下面通过一个使用嵌套内置类型实现业务逻辑,体验下它随着业务越来越复杂,操作嵌套内置类型数据代码也变得复杂。

    2.1.第一版需求

    在一个考试系统中,实现增加学生信息,添加学生所有考试分数,计算学生的平均分。

    # 创建一个计算学生平均分的类
    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'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行上面的代码,输出了学生的平均分

    94.66666666666667
    
    • 1

    2.2.第二版需求

    在第一版需求上添加新的需求,将学生的学分按照课程添加,同时在每次考试科目里增加权重,根据科目的权重计算出平均分。

    • 新需求的复杂度增加了,存储数据的内置类型嵌套层级也增加了复杂度。{‘name’:{‘subject’:[(score, weight)]}}
    • 添加学生分数的方法,要增加科目、权重属性,将数据封装到嵌套的内置类型中
    • 计算平均分的方法为了处理嵌套内置类型的数据代码变得非常复杂
    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'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    3.多层嵌套内置类型重构为类体系

    使用嵌套类重构代码,它的设计思想归结到一点,就是用类对象替代内置类型存储数据。
    嵌套类重构开发思路:

    • 拆分对象,将数据对象拆分到类对象。
      • 课程类对象存储课程分数和权重
      • 学生类对象存储学生各科目的成绩
    1.构建底层数据结构

    先从依赖书的最底层开始,考虑怎么存储某科目的单次考试成绩与权重。由于分数不需要修改,因此可以使用元组记录考试成绩和权重。
    下面来看一个例子

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面的例子,在计算total_weight时遍历grades元组score不需要因此使用_忽律,并且元组传参使用数字,不容易看出对应的是哪个参数。因此我们可以使用具名元组类代替普通的元组,存储分数和权重。

    from collections import namedtuple
    Grade = namedtuple('Grade', ('score', 'weight'))
    
    • 1
    • 2
    2.创建类

    我们的数据中包含了学生对象、课程对象、分数权重对象、其中分数和权重非常简单不需要创建类,用一个数组来存储数据。其余的每个对象都对应的创建一个类存储对象数据。

    创建课程类

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    创建学生类

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    创建成绩册类
    将学生名称和学生对象关联

    class Gradebook:
        def __init__(self):
            self._students = defaultdict(Student)
    
        def get_student(self, name):
            return self._students[name]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用重构的组合类代码

    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())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出结果

    88.18092691622104
    
    • 1
  • 相关阅读:
    Java之~批量压缩文件为InputStream方式上传到云服务
    论文实验可视化方法
    Linux之shell条件测试
    python列表基本操作之增删
    qt 绘图
    System verilog从Testbench中dump出所需要的数据代码
    游戏开发30课 cocoscreator骨骼贴图布局设置
    【2023最新版】超详细NMAP安装保姆级教程,Nmap的介绍、功能并进行网络扫描,收藏这一篇就够了
    MySQL备份与恢复
    LVS的认识与快速上手
  • 原文地址:https://blog.csdn.net/m0_38039437/article/details/128025344