python是少数支持多重继承的现代编程语言之一。多重继承是同时从多个基类派生一个类的能力

多重继承的名声很坏,以至于大多数现代编程语言都不支持它。相反,现代编程语言支持接口的概念。在这些语言中,您从单个基类继承,然后实现多个接口,因此您的类可以在不同的情况下重用

这种方法给您的设计带来了一些限制。您只能通过直接派生一个类来继承该类的实现。您可以实现多个接口,但不能继承多个类的实现

这个约束对软件设计是有好处的,因为它迫使您在设计类时减少相互之间的依赖。您将在本文后面看到,您可以通过组合利用多个实现,这使得软件更加灵活。然而,这一节是关于多重继承的,所以让我们来看看它是如何工作的

事实证明,有时临时秘书是在有太多文书工作要做的时候才被雇佣的。临时秘书类在生产力系统的上下文中扮演秘书的角色,但出于工资单的目的,它是HourlyEmployee

派生自Secretary:您可以派生自Secretary,以继承角色的.work()方法,然后覆盖.calculate_payroll()方法,将其实现为HourlyEmployee

HourlyEmployee派生:您可以从HourlyEmployee派生以继承.calculate_payroll()方法,然后重写.work()方法以将其实现为秘书


 (, ):

Python允许您通过在类声明中的括号之间指定它们来从两个不同的类继承

现在,您修改程序以添加新的临时秘书员工

 
 
 

  .(, , )
  .(, , )
  .(, , , )
  .(, , , )
  .(, , , )
  [
    ,
    ,
    ,
    ,
    ,
]
  .()
.(, )
  .()
.()

运行程序

$  .

 (   ):
  ,  ,  
    .(, , , )
: ()

您会收到一个TypeError异常,该异常表示应有4个位置参数,但给出了5个

这是因为您首先从秘书中派生了TemporarySecretary,然后从HourlyEmployee中派生了,所以解释器试图使用Secretary .__ init __()来初始化对象。

好吧,我们扭转一下

 (, ):

运行程序

$  .

 (   ):
  ,  ,  
    .(, , , )
  ,  ,  
  ().(, )
: ()     :

现在看来,您缺少了一个周秘书参数,该参数对于初始化局长是必需的,但是在TemporarySecretary的上下文中该参数没有意义,因为它是HourlyEmployee

也许实现TemporarySecretary .__ init __()会有所帮助


 (, ):
     (, , , , ):
        ().(, , , )

try it

$  .

 (   ):
  ,  ,  
    .(, , , )
  ,  ,  
  ().(, , , )
  ,  ,  
  ().(, )
: ()     :

这也不管用。好了,现在是深入研究Python的方法解析顺序(MRO)的时候了,看看发生了什么

当访问类的方法或属性时,Python使用类MRO来查找它。super()还使用MRO来确定调用哪个方法或属性。您可以使用Python super()Supercharge类中了解关于super()的更多信息

   
.

( ,
  ,
  ,
  ,
  ,
  
)

MRO显示Python查找匹配的属性或方法的顺序。在示例中,这就是我们创建TemporarySecretary对象时发生的情况

  1. 调用TemporarySecretary .__ init __(self,id,name,hours_worked,hour_rate)方法

  2. super().__ init __(id,name,hours_worked,hour_rate)调用与HourlyEmployee .__ init __(self,id,name,hour_worked,hour_rate)

  3. HourlyEmployee调用super().__ init __(id,name),MRO将与秘书匹配。秘书.__ init __(),它继承自SalaryEmployee .__ init __(self,id,name,weekly_salary)

由于参数不匹配,因此引发TypeError异常

您可以通过反转继承顺序并直接调用HourlyEmployee .__ init __()来绕过MRO,如下所示

 (, ):
     (, , , , ):
        .(, , , , )

这就解决了创建对象的问题,但是在尝试计算薪资时会遇到类似的问题。您可以运行该程序以查看问题

$  .

  

       .
       .
       .
      .
       .

 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
 (   ):
   ,  ,  
    .()
   ,  ,  
    ()
   ,  ,  
     .
:

现在的问题是,由于您颠倒了继承顺序,MRO将在HourlyEmployee中的SalariedEmployee方法之前找到SalariedEmployee.calculate_payroll()方法。您需要在TemporarySecretary中覆盖.calculate_payroll()并从中调用正确的实现

 (, ):
     (, , , , ):
        .(, , , , )

     ():
         .()

compute_payroll()方法直接调用HourlyEmployee.calculate_payroll()以确保获得正确的结果。您可以再次运行该程序以查看其是否正常运行

$  .

  

       .
       .
       .
      .
       .

 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
  :

程序现在可以正常工作了,因为您可以通过显式地告诉解释器我们想要使用哪个方法来强制方法解析顺序。

正如您所看到的,多重继承可能令人困惑,特别是当您遇到diamond问题时


该图显示了当前类设计的diamond问题。TemporarySecretary使用多重继承派生自两个类,这两个类最终也派生自Employee。这将导致两条路径到达Employee基类,这是您希望在设计中避免的

当您使用多重继承并从两个具有公共基类的类派生时,diamond问题就会出现。这可能导致调用方法的错误版本

正如您所看到的,Python提供了一种方法来强制调用正确的方法,并且分析MRO可以帮助您理解问题

Employee派生类由两个不同的系统使用

  1. 跟踪员工生产力的生产力系统

  2. 计算员工薪资的薪资系统

这意味着与生产力相关的所有内容都应该放在一个模块中,而与工资相关的所有内容都应该放在另一个模块中。您可以开始更改生产力模块


 :
     (, , ):
        ()
        ()
           :
              .()
            ()
        ()

 :
     (, ):
         

 :
     (, ):
         

 :
     (, ):
         

 :
     (, ):

生产力模块实现ProductivitySystem类及其支持的相关角色。这些类实现了系统所需的work()接口,但它们不是从Employee派生的


 :
     (, ):
        ()
        ()
           :
            ()
            ()
            ()

 :
     (, ):
        .  

     ():
         .

 :
     (, , ):
        .  
        .  

     ():
         .  .

 ():
     (, , ):
        ().()
        .  

     ():
          ().()
           .

hr模块实现了PayrollSystem,该系统为员工计算工资。它还实现了工资单的策略类。如您所见,策略类别不再源自Employee


   (
    ,
    ,
    
)
   (
    ,
    ,
    ,
    
)

 :
     (, , ):
        .  
        .  

 (, , ):
     (, , , ):
        .(, )
        ().(, )

 (, , ):
     (, , , ):
        .(, )
        ().(, )

 (, , ):
     (, , , , ):
        .(, , )
        ().(, )

 (, , ):
     (, , , , ):
        .(, , )
        ().(, )

 (, , ):
     (, , , , ):
        .(, , )
        ().(, )

employees模块从其他模块导入策略和角色,并实现不同的Employee类型。您仍然使用多重继承来继承salary策略类和productivity角色的实现,但是每个类的实现只需要处理初始化

注意,您仍然需要在构造函数中显式地初始化薪水策略。您可能看到Manager和Secretary的初始化是相同的。另外,factory - workerTemporarySecretary的初始化是相同的

您将不希望在更复杂的设计中使用这种代码重复,因此在设计类层次结构时必须小心

运行程序

$  .

  

 :      .
 :      .
 :      .
 :     .
 :      .

 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
  : 

 :    
  :