专栏首页志学Python一起来探讨 python 类爆炸问题

一起来探讨 python 类爆炸问题

我曾羡慕犬夜叉,我曾以为我会成为他,哈哈

废话少说,还是接着昨天的问题,书写吧

连载 Python OOP指南(1)

运行程序输出如下

$ python program.py

Calculating Payroll
===================
Payroll for: 1 - John Smith
- Check amount: 1500

Payroll for: 2 - Jane Doe
- Check amount: 600

Payroll for: 3 - Kevin Bacon
- Check amount: 1250

Payroll for: 20000 - Anonymous
- Check amount: 1000000

如您所见,PayrollSystem仍然可以处理新对象,因为它符合所需的接口

由于您不必从特定的类派生对象就可以被程序重用,因此您可能会问为什么应该使用继承而不是仅实现所需的接口。以下规则可能对您有帮助

使用继承来重用实现:您的派生类应该利用它们的大部分基类实现。他们还必须为is a relationship建模。客户类也可能有一个id和一个名称,但是客户不是雇员,所以您不应该使用继承。

实施要重用的接口:当您希望类被应用程序的特定部分重用时,您可以在类中实现所需的接口,但是无需提供基类或从另一个类继承

# In hr.py

class PayrollSystem:
    def calculate_payroll(self, employees):
        print('Calculating Payroll')
        print('===================')
        for employee in employees:
            print(f'Payroll for: {employee.id} - {employee.name}')
            print(f'- Check amount: {employee.calculate_payroll()}')
            print('')

class Employee:
    def __init__(self, id, name):
        self.id = id
        self.name = name

class SalaryEmployee(Employee):
    def __init__(self, id, name, weekly_salary):
        super().__init__(id, name)
        self.weekly_salary = weekly_salary

    def calculate_payroll(self):
        return self.weekly_salary

class HourlyEmployee(Employee):
    def __init__(self, id, name, hours_worked, hour_rate):
        super().__init__(id, name)
        self.hours_worked = hours_worked
        self.hour_rate = hour_rate

    def calculate_payroll(self):
        return self.hours_worked * self.hour_rate

class CommissionEmployee(SalaryEmployee):
    def __init__(self, id, name, weekly_salary, commission):
        super().__init__(id, name, weekly_salary)
        self.commission = commission

    def calculate_payroll(self):
        fixed = super().calculate_payroll()
        return fixed + self.commission

您删除了abc模块的导入,因为Employee类不需要是抽象的。您还从其中删除了抽象的calculate_payroll()方法,因为它不提供任何实现

基本上,您继承了派生类中Employee类的id和name属性的实现。因为.calculate_payroll()只是PayrollSystem.calculate_payroll()方法的一个接口,所以不需要在Employee基类中实现它

注意commission类是如何从SalaryEmployee派生出来的。这意味着commonemployee继承了SalaryEmployee的实现和接口。可以看到,calculate_payroll()方法如何利用基类实现,因为它依赖于super().calculate_payroll()的结果来实现自己的版本

类爆炸问题

如果您不小心,继承会导致您进入难以理解和维护的巨大的类层次结构。这称为类爆炸问题

您开始构建薪资系统用于计算薪资的Employee类型的类层次结构。现在,您需要向这些类添加一些功能,以便它们可以与新的ProductivitySystem一起使用

ProductivitySystem根据员工角色跟踪生产力。有不同的员工角色

经理:他们四处走走,大喊大叫,告诉他们该怎么做。他们是受薪雇员,可赚更多钱

秘书:他们为经理完成所有书面工作,并确保一切按时计费和付款。他们也是受薪员工,但赚的钱少

销售员工:他们打很多电话来销售产品。他们有薪水,但他们也会得到销售佣金

工厂工人:他们为公司生产产品。他们按小时支付

有了这些要求,您开始发现Employee及其派生类可能属于hr模块之外的其他位置,因为现在ProductivitySystem也使用了它们

您创建一个employees模块并将类移到那里

# In employees.py

class Employee:
    def __init__(self, id, name):
        self.id = id
        self.name = name

class SalaryEmployee(Employee):
    def __init__(self, id, name, weekly_salary):
        super().__init__(id, name)
        self.weekly_salary = weekly_salary

    def calculate_payroll(self):
        return self.weekly_salary

class HourlyEmployee(Employee):
    def __init__(self, id, name, hours_worked, hour_rate):
        super().__init__(id, name)
        self.hours_worked = hours_worked
        self.hour_rate = hour_rate

    def calculate_payroll(self):
        return self.hours_worked * self.hour_rate

class CommissionEmployee(SalaryEmployee):
    def __init__(self, id, name, weekly_salary, commission):
        super().__init__(id, name, weekly_salary)
        self.commission = commission

    def calculate_payroll(self):
        fixed = super().calculate_payroll()
        return fixed + self.commission

实现保持不变,但是将类移动到employee模块。现在,您更改您的程序以支持更改

# In program.py

import hr
import employees

salary_employee = employees.SalaryEmployee(1, 'John Smith', 1500)
hourly_employee = employees.HourlyEmployee(2, 'Jane Doe', 40, 15)
commission_employee = employees.CommissionEmployee(3, 'Kevin Bacon', 1000, 250)
payroll_system = hr.PayrollSystem()
payroll_system.calculate_payroll([
    salary_employee,
    hourly_employee,
    commission_employee
])

您运行该程序并验证它仍然可以工作

$ python program.py

Calculating Payroll
===================
Payroll for: 1 - John Smith
- Check amount: 1500

Payroll for: 2 - Jane Doe
- Check amount: 600

Payroll for: 3 - Kevin Bacon
- Check amount: 1250

一切就绪后,您就可以开始添加新类了

# In employees.py

class Manager(SalaryEmployee):
    def work(self, hours):
        print(f'{self.name} screams and yells for {hours} hours.')

class Secretary(SalaryEmployee):
    def work(self, hours):
        print(f'{self.name} expends {hours} hours doing office paperwork.')

class SalesPerson(CommissionEmployee):
    def work(self, hours):
        print(f'{self.name} expends {hours} hours on the phone.')

class FactoryWorker(HourlyEmployee):
    def work(self, hours):
        print(f'{self.name} manufactures gadgets for {hours} hours.')

首先,添加一个从SalaryEmployee派生的Manager类。该类公开了将由生产力系统使用的work()方法。该方法占用员工工作时间

然后添加SecretarySalesPersonFactoryWorker,然后实现work()接口,以便生产力系统可以使用它们。

现在,您可以添加ProductivitySytem

# In productivity.py

class ProductivitySystem:
    def track(self, employees, hours):
        print('Tracking Employee Productivity')
        print('==============================')
        for employee in employees:
            employee.work(hours)
        print('')

该类使用track()方法跟踪员工,该方法获取员工列表和要跟踪的小时数。您现在可以将生产力系统添加到程序中

# In program.py

import hr
import employees
import productivity

manager = employees.Manager(1, 'Mary Poppins', 3000)
secretary = employees.Secretary(2, 'John Smith', 1500)
sales_guy = employees.SalesPerson(3, 'Kevin Bacon', 1000, 250)
factory_worker = employees.FactoryWorker(2, 'Jane Doe', 40, 15)
employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
]
productivity_system = productivity.ProductivitySystem()
productivity_system.track(employees, 40)
payroll_system = hr.PayrollSystem()
payroll_system.calculate_payroll(employees)

该程序将创建不同类型的员工列表。员工名单被发送到生产力系统以跟踪他们40个小时的工作。然后,将相同的员工列表发送到薪资系统以计算其薪资

$ python program.py

Tracking Employee Productivity
==============================
Mary Poppins screams and yells for 40 hours.
John Smith expends 40 hours doing office paperwork.
Kevin Bacon expends 40 hours on the phone.
Jane Doe manufactures gadgets for 40 hours.

Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000

Payroll for: 2 - John Smith
- Check amount: 1500

Payroll for: 3 - Kevin Bacon
- Check amount: 1250

Payroll for: 4 - Jane Doe
- Check amount: 600

该程序显示员工通过生产力系统工作40个小时。然后计算并显示每个员工的工资单

程序按预期工作,但是您必须添加四个新类来支持更改。随着新需求的出现,您的类层次结构将不可避免地增长,从而导致类爆炸问题,您的层次结构将变得非常大,以至于难以理解和维护

下图显示了新的类层次结构

本文分享自微信公众号 - 志学Python(lijinwen1996329ken),作者:志学Python

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 破解wifi密码

    你有没有因为网费的问题,没装网络,紧靠手机过日子,有没有附近有很多WIFI,你却用不了,是不是很痛苦,是不是很难受

    公众号---志学Python
  • 将特性与Mixin类混合

    python中多重继承的用途之一是通过mixins扩展类功能。mixin是提供其他类方法的类,但不被视为基类

    公众号---志学Python
  • 灵活的设计组合

    组合比继承更灵活,因为它可以建模松散耦合的关系。对组件类的更改对复合类影响很小或没有影响。基于组成的设计更适合更改

    公众号---志学Python
  • iOS下WebRTC音视频通话(三)-音视频通话过程的分析补充

    前两篇文章记录了音视频通话的一些概念和一些流程,以及一个局域网内音视频通话的示例。 今天以一个伪真实网络间的音视频通话示例,来分析WebRTC音视频通话的过程...

    Haley_Wong
  • 类实例:搬家具

    skylark
  • iOS下WebRTC音视频通话(二)-局域网内音视频通话准备开始着手开发接收方

    这里是iOS 下WebRTC音视频通话开发的第二篇,在这一篇会利用一个局域网内音视频通话的例子介绍WebRTC中常用的API。 如果你下载并编译完成之后,会看...

    Haley_Wong
  • 自定义UITableViewCell实现左滑动多菜单功能LeftSwipe

    1、使用自定义UITableViewCell + UISwipeGestureRecognizer + 代理 实现;

    tandaxia
  • iOS开发之画图板(贝塞尔曲线)

      贝塞尔曲线,听着挺牛气一词,不过下面我们在做画图板的时候就用到贝塞尔绘直线,没用到绘制曲线的功能。如果会点PS的小伙伴会对贝塞尔曲线有更直观的理解。这篇博文...

    lizelu
  • python爬虫入门之爬万本书籍

    Python现在非常火,语法简单而且功能强大,很多同学都想学Python!所以小的给各位看官们准备了高价值Python学习视频教程及相关电子版书籍,都放在了文章...

    python学习教程
  • python pyqt5 QTabWidget

    import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.Qt...

    用户5760343

扫码关注云+社区

领取腾讯云代金券