--- title: Inheritance And Composition fid: 20240128-204810 tags: --- (Inheritance-And-Composition)= # Inheritance And Composition 2024-01-28 [Inheritance and Composition: A Python OOP Guide – Real Python](https://realpython.com/inheritance-composition-python/) ### What’s Inheritance? is_a - Classes that inherit from another are called derived classes, subclasses, or subtypes. - Classes from which other classes are derived are called base classes or super classes. - A derived class is said to derive, inherit, or extend a base class. 这就是所谓的利斯科夫替换原则。该原则指出,如果 S 是 T 的子类型,那么用 S 类型的对象替换 T 类型的对象不会改变程序的行为。 在创建类层次结构时,你应该始终遵循利斯科夫替换原则,否则你会遇到很多问题。 [SOLID Principles: Improve Object-Oriented Design in Python – Real Python](https://realpython.com/solid-principles-python/#liskov-substitution-principle-lsp) ### What’s Composition? has_a 组合是一个建立关系模型的概念。它可以通过组合其他类型的对象来创建复杂类型。这意味着一个 Composite 类可以包含另一个 Component 类的对象。 包含其他类对象的类通常被称为复合类,而用于创建更复杂类型的类则被称为组件。 ### 所有类都继承自 object 完整但冗余(不必要)的写法: ```python class EmptyClass(object): ``` cls 与 obj 有几乎相同的成员: ```python >>> class EmptyClass: ... pass ... >>> cls = EmptyClass() >>> dir(cls) ``` ``` Python >>> obj = object() >>> dir(obj) ``` ### Exceptions Are an Exception 在 Python 中创建的每个类都隐式地派生自 object。但是,有一个例外:用于通过引发异常来指示错误的类。 ```python >>> class NotAnError: ... pass ... >>> raise NotAnError() Traceback (most recent call last): ... TypeError: exceptions must derive from BaseException ``` 正确的做法只能是: ```python >>> class AnError(Exception): ... pass ... >>> raise AnError() Traceback (most recent call last): ... AnError ``` ### Creating Class Hierarchies ```python 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("") ``` ```python # ... class Employee: def __init__(self, id, name): self.id = id self.name = name ``` ```python # ... 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 ``` ```python # ... class HourlyEmployee(Employee): def __init__(self, id, name, hours_worked, hourly_rate): super().__init__(id, name) self.hours_worked = hours_worked self.hourly_rate = hourly_rate def calculate_payroll(self): return self.hours_worked * self.hourly_rate ``` ```python # ... 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 ``` ![](https://xnotes-1304436219.cos.ap-nanjing.myqcloud.com/notes_image/ic-initial-employee-inheritance.b5f1e65cb8d1.jpg) ```python import hr salary_employee = hr.SalaryEmployee(1, "John Smith", 1500) hourly_employee = hr.HourlyEmployee(2, "Jane Doe", 40, 15) commission_employee = hr.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 ``` ### Abstract Base Classes in Python The `Employee` class in the example above is what is called an abstract base class. Abstract base classes exist to be inherited, but never instantiated. Python provides the `abc` module to formally define abstract base classes. ```python from abc import ABC, abstractmethod # ... class Employee(ABC): def __init__(self, id, name): self.id = id self.name = name @abstractmethod def calculate_payroll(self): pass ``` 您从 ABC 派生出 Employee,使其成为一个抽象基类。然后,使用 @abstractmethod 装饰器装饰 .calculate_payroll() 方法。 这一变化有两个很好的副作用: 你在告诉模块的用户不能创建 Employee 类型的对象。 你是在告诉其他开发 hr 模块的开发人员,如果他们派生自 Employee,那么他们必须覆盖 .calculate_payroll() 抽象方法。 ```python >>> import hr >>> employee = hr.Employee(1, "Abstract") Traceback (most recent call last): ... TypeError: Can't instantiate abstract class Employee ⮑ with abstract method calculate_payroll ``` ## 实现继承与接口继承 当你从一个类派生出另一个类时,派生类会继承以下两个方面: 基类接口:派生类继承基类的所有方法、属性和属性。 基类实现:派生类继承实现类接口的代码。 大多数情况下,你会希望继承一个类的实现,但你也会希望实现多个接口,以便在不同情况下使用你的对象。现代编程语言在设计时就考虑到了这一基本概念。它们允许你继承一个类,但可以实现多个接口。 ### Duck typing ```python class DisgruntledEmployee: def __init__(self, id, name): self.id = id self.name = name def calculate_payroll(self): return 1_000_000 ``` DisgruntledEmployee 类并不是从 Employee 派生的,但它提供了与 PayrollSystem 所需的相同接口。 ```{code-block} python :emphasize-lines: 2,7,15 import hr import disgruntled salary_employee = hr.SalaryEmployee(1, "John Smith", 1500) hourly_employee = hr.HourlyEmployee(2, "Jane Doe", 40, 15) commission_employee = hr.CommissionEmployee(3, "Kevin Bacon", 1000, 250) disgruntled_employee = disgruntled.DisgruntledEmployee(20000, "Anonymous") payroll_system = hr.PayrollSystem() payroll_system.calculate_payroll( [ salary_employee, hourly_employee, commission_employee, disgruntled_employee, ] ) ``` PayrollSystem 仍然可以处理新对象,因为它符合所需的接口。 既然不必派生自特定的类,程序就可以重用你的对象,那么你可能会问,为什么要使用继承而不是仅仅实现所需的接口呢? 使用继承重用实现:派生类应充分利用基类的大部分实现。它们还必须模拟一种关系。客户 "类可能也有 .id 和 .name,但 "客户 "不是 "员工",因此在这种情况下,不应使用继承。 实现要重用的接口:当你希望你的类被应用程序的某个特定部分重用时,你可以在你的类中实现所需的接口,但你不需要提供一个基类,也不需要从其他类继承。 ### 重构 基本上,您是在派生类中继承 Employee 类的 .id 和 .name 属性的实现。由于 .calculate_payroll() 只是 PayrollSystem.calculate_payroll() 方法的一个接口,因此无需在 Employee 基类中实现它。 删除了 abc 模块的导入,因为 Employee 类不需要抽象。 还删除了抽象的 .calculate_payroll() 方法,因为它没有提供任何实现。 ```python 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, hourly_rate): super().__init__(id, name) self.hours_worked = hours_worked self.hourly_rate = hourly_rate def calculate_payroll(self): return self.hours_worked * self.hourly_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 ``` ## 类爆炸问题 If you’re not careful, inheritance can lead you to a huge hierarchical class structure that’s hard to understand and maintain. This is known as the **class explosion problem**. 现在,您需要为这些类添加一些功能,以便在新的 ProductivitySystem 中使用它们。ProductivitySystem 可根据员工角色跟踪生产率。员工有不同的角色: - **Managers:** They walk around yelling at people, telling them what to do. They’re salaried employees and make more money. - **Secretaries:** They do all the paperwork for managers and ensure that everything gets billed and payed on time. They’re also salaried employees but make less money. - **Sales employees:** They make a lot of phone calls to sell products. They have a salary, but they also get commissions for sales. - **Factory workers:** They manufacture the products for the company. They’re paid by the hour. ```python # 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, hourly_rate): super().__init__(id, name) self.hours_worked = hours_worked self.hourly_rate = hourly_rate def calculate_payroll(self): return self.hours_worked * self.hourly_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 ``` 实现方法保持不变,但将类移至 imployees 模块。 hr 模块也变小了: ```python # 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("") ``` ```python # 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] ) ``` 在 employee 模块中添加新类: ```python # ... 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.") ``` ```python # productivity.py class ProductivitySystem: def track(self, employees, hours): print("Tracking Employee Productivity") print("==============================") for employee in employees: employee.work(hours) print("") ``` ```python # 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(4, "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) ``` ![](https://xnotes-1304436219.cos.ap-nanjing.myqcloud.com/notes_image/ic-class-explosion.a3d42b8c9b91.jpg) ### Inheriting Multiple Classes ## Reference [Supercharge Your Classes With Python super() – Real Python](https://realpython.com/python-super/)