<jupyter_start><jupyter_text>Try with Employee class<jupyter_code>employee = Employee(1, 'Invalid')
payroll_system = PayrollSystem()
payroll_system.calculate_payroll([employee])<jupyter_output>Calculating Payroll
===================
Payroll for: 1 - Invalid<jupyter_text>Abstract base class<jupyter_code>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
employee = Employee(1, 'Abstract')<jupyter_output><empty_output><jupyter_text>The output shows that the class cannot be instantiated because it contains an abstract method calculate_payroll(). Derived classes must override the method to allow creating objects of their type.  Implementation Inheritance vs Interface Inheritance The DisgruntledEmployee class doesn’t derive from Employee, but it exposes the same interface required by the PayrollSystem. The PayrollSystem.calculate_payroll() requires a list of objects that implement the following interface:An id property or attribute that returns the employee’s idA name property or attribute that represents the employee’s nameA .calculate_payroll() method that doesn’t take any parameters and returns the payroll amount to processAll these requirements are met by the DisgruntledEmployee class, so the PayrollSystem can still calculate its payroll<jupyter_code>class DisgruntledEmployee:
    def __init__(self, id, name):
        self.id = id
        self.name = name

    def calculate_payroll(self):
        return 1000000

salary_employee = SalaryEmployee(1, '<NAME>', 1500)
hourly_employee = HourlyEmployee(2, '<NAME>', 40, 15)
commission_employee = CommissionEmployee(3, '<NAME>', 1000, 250)
disgruntled_employee = DisgruntledEmployee(20000, 'Anonymous')
payroll_system = PayrollSystem()
payroll_system.calculate_payroll([
    salary_employee,
    hourly_employee,
    commission_employee,
    disgruntled_employee
])<jupyter_output>Calculating Payroll
===================
Payroll for: 1 - <NAME>
- Check amount: 1500

Payroll for: 2 - <NAME>
- Check amount: 600

Payroll for: 3 - <NAME>
- Check amount: 1250

Payroll for: 20000 - Anonymous
- Check amount: 1000000<jupyter_text>When to use inheritanceUse inheritance to reuse an implementation: Your derived classes should leverage most of their base class implementation. They must also model an **is a** relationship. A Customer class might also have an id and a name, but a Customer is not an Employee, so you should not use inheritance.Implement an interface to be reused: When you want your class to be reused by a specific part of your application, you implement the required interface in your class, but you don’t need to provide a base class, or inherit from another class.  Reset code<jupyter_code>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<jupyter_output><empty_output><jupyter_text>Notice how the CommissionEmployee class derives from SalaryEmployee. This means that CommissionEmployee inherits the implementation and interface of SalaryEmployee. You can see how the CommissionEmployee.calculate_payroll() method leverages the base class implementation because it relies on the result from super().calculate_payroll() to implement its own version.  Class explosion problemIf you are not careful, inheritance can lead you to a huge hierarchical structure of classes that is hard to understand and maintain. This is known as the class explosion problem.You started building a class hierarchy of Employee types used by the PayrollSystem to calculate payroll. Now, you need to add some functionality to those classes, so they can be used with the new ProductivitySystem.The ProductivitySystem tracks productivity based on employee roles. There are different employee roles:* Managers: They walk around yelling at people telling them what to do. They are salaried employees and make more money.* Secretaries: They do all the paper work for managers and ensure that everything gets billed and payed on time. They are 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 are paid by the hour.With those requirements, you start to see that Employee and its derived classes might belong somewhere other than the hr module because now they’re also used by the ProductivitySystem.You create an employees module and move the classes there:<jupyter_code># 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

# In program.py

salary_employee = SalaryEmployee(1, '<NAME>', 1500)
hourly_employee = HourlyEmployee(2, '<NAME>', 40, 15)
commission_employee = CommissionEmployee(3, '<NAME>', 1000, 250)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll([
    salary_employee,
    hourly_employee,
    commission_employee
])
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.')
# In productivity.py

class ProductivitySystem:
    def track(self, employees, hours):
        print('Tracking Employee Productivity')
        print('==============================')
        for employee in employees:
            employee.work(hours)
        print('')
manager = Manager(1, '<NAME>', 3000)
secretary = Secretary(2, '<NAME>', 1500)
sales_guy = SalesPerson(3, '<NAME>', 1000, 250)
factory_worker = FactoryWorker(2, '<NAME>', 40, 15)

employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
]

productivity_system = ProductivitySystem()
productivity_system.track(employees, 40)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll(employees)<jupyter_output>Tracking Employee Productivity
==============================
<NAME> screams and yells for 40 hours.
<NAME> expends 40 hours doing office paperwork.
<NAME> expends 40 hours on the phone.
<NAME> manufactures gadgets for 40 hours.

Calculating Payroll
===================
Payroll for: 1 - <NAME>
- Check amount: 3000

Payroll for: 2 - <NAME>
- Check amount: 1500

Payroll for: 3 - <NAME>
- Check amount: 1250

Payroll for: 2 - <NAME>
- Check amount: 600<jupyter_text>The program works as expected, but you had to add four new classes to support the changes. As new requirements come, your class hierarchy will inevitably grow, leading to the class explosion problem where your hierarchies will become so big that they’ll be hard to understand and maintain.The following diagram shows the new class hierarchy:![Class Explosion](img/class_explosion.jpg)  Multiple inheritanceIt turns out that sometimes temporary secretaries are hired when there is too much paperwork to do. The TemporarySecretary class performs the role of a Secretary in the context of the ProductivitySystem, but for payroll purposes, it is an HourlyEmployee.You look at your class design. It has grown a little bit, but you can still understand how it works. It seems you have two options:Derive from Secretary: You can derive from Secretary to inherit the .work() method for the role, and then override the .calculate_payroll() method to implement it as an HourlyEmployee.Derive from HourlyEmployee: You can derive from HourlyEmployee to inherit the .calculate_payroll() method, and then override the .work() method to implement it as a Secretary.  Other code useful to keep in mind whilst reviewing TemporarySecretary<jupyter_code>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 Secretary(SalaryEmployee):
    def work(self, hours):
        print(f'{self.name} expends {hours} hours doing office paperwork.')
    
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 TemporarySecretary(HourlyEmployee, Secretary):
    def __init__(self, id, name, hours_worked, hour_rate):
        super().__init__(id, name, hours_worked, hour_rate)
manager = Manager(1, '<NAME>', 3000)
secretary = Secretary(2, '<NAME>', 1500)
sales_guy = SalesPerson(3, '<NAME>', 1000, 250)
factory_worker = FactoryWorker(4, '<NAME>', 40, 15)
temporary_secretary = TemporarySecretary(5, '<NAME>', 40, 9)

company_employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
    temporary_secretary,
]

productivity_system = ProductivitySystem()
productivity_system.track(company_employees, 40)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll(company_employees)
TemporarySecretary.__mro__<jupyter_output><empty_output><jupyter_text>The MRO shows the order in which Python is going to look for a matching attribute or method. In the example, this is what happens when we create the TemporarySecretary object:1. The TemporarySecretary.__init__(self, id, name, hours_worked, hour_rate) method is called.2. The super().\_\_init\_\_(id, name, hours_worked, hour_rate) call matches HourlyEmployee.\_\_init\_\_(self, id, name, hour_worked, hour_rate).3. HourlyEmployee calls super().\_\_init\_\_(id, name), which the MRO is going to match to Secretary.\_\_init\_\_(), which is inherited from SalaryEmployee.\_\_init\_\_(self, id, name, weekly_salary).Because the parameters don’t match, a TypeError exception is raised.You can bypass the MRO by reversing the inheritance order and directly calling HourlyEmployee.__init__() as follows:  Changing MRO<jupyter_code>class TemporarySecretary(Secretary, HourlyEmployee):
    def __init__(self, id, name, hours_worked, hour_rate):
        HourlyEmployee.__init__(self, id, name, hours_worked, hour_rate)  
manager = Manager(1, '<NAME>', 3000)
secretary = Secretary(2, '<NAME>', 1500)
sales_guy = SalesPerson(3, '<NAME>', 1000, 250)
factory_worker = FactoryWorker(4, '<NAME>', 40, 15)
temporary_secretary = TemporarySecretary(5, '<NAME>', 40, 9)

company_employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
    temporary_secretary,
]

productivity_system = ProductivitySystem()
productivity_system.track(company_employees, 40)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll(company_employees)
class TemporarySecretary(Secretary, HourlyEmployee):
    def __init__(self, id, name, hours_worked, hour_rate):
        HourlyEmployee.__init__(self, id, name, hours_worked, hour_rate)

    def calculate_payroll(self):
        return HourlyEmployee.calculate_payroll(self)
manager = Manager(1, '<NAME>', 3000)
secretary = Secretary(2, '<NAME>', 1500)
sales_guy = SalesPerson(3, '<NAME>', 1000, 250)
factory_worker = FactoryWorker(4, '<NAME>', 40, 15)
temporary_secretary = TemporarySecretary(5, '<NAME>', 40, 9)

company_employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
    temporary_secretary,
]

productivity_system = ProductivitySystem()
productivity_system.track(company_employees, 40)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll(company_employees)<jupyter_output>Tracking Employee Productivity
==============================
<NAME> screams and yells for 40 hours.
<NAME> expends 40 hours doing office paperwork.
<NAME> expends 40 hours on the phone.
<NAME> manufactures gadgets for 40 hours.
<NAME> expends 40 hours doing office paperwork.

Calculating Payroll
===================
Payroll for: 1 - <NAME>
- Check amount: 3000

Payroll for: 2 - <NAME>
- Check amount: 1500

Payroll for: 3 - <NAME>
- Check amount: 1250

Payroll for: 4 - <NAME>
- Check amount: 600

Payroll for: 5 - <NAME>
- Check amount: 360<jupyter_text>The program now works as expected because you’re forcing the method resolution order by explicitly telling the interpreter which method we want to use.As you can see, multiple inheritance can be confusing, especially when you run into the diamond problem.![](./img/diamond.jpg) The diagram shows the diamond problem with the current class design. TemporarySecretary uses multiple inheritance to derive from two classes that ultimately also derive from Employee. This causes two paths to reach the Employee base class, which is something you want to avoid in your designs.The diamond problem appears when you’re using multiple inheritance and deriving from two classes that have a common base class. This can cause the wrong version of a method to be called.As you’ve seen, Python provides a way to force the right method to be invoked, and analyzing the MRO can help you understand the problem.Still, when you run into the diamond problem, it’s better to re-think the design. You will now make some changes to leverage multiple inheritance, avoiding the diamond problem.The Employee derived classes are used by two different systems:The productivity system that tracks employee productivity.The payroll system that calculates the employee payroll.This means that everything related to productivity should be together in one module and everything related to payroll should be together in another. You can start making changes to the productivity module:<jupyter_code># In productivity.py

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

class ManagerRole:
    def work(self, hours):
        return f'screams and yells for {hours} hours.'

class SecretaryRole:
    def work(self, hours):
        return f'expends {hours} hours doing office paperwork.'

class SalesRole:
    def work(self, hours):
        return f'expends {hours} hours on the phone.'

class FactoryRole:
    def work(self, hours):
        return f'manufactures gadgets for {hours} hours.'<jupyter_output><empty_output><jupyter_text>The classes implement the work() interface required by the system, but they don’t derived from Employee.<jupyter_code># 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 SalaryPolicy:
    def __init__(self, weekly_salary):
        self.weekly_salary = weekly_salary

    def calculate_payroll(self):
        return self.weekly_salary

class HourlyPolicy:
    def __init__(self, hours_worked, hour_rate):
        self.hours_worked = hours_worked
        self.hour_rate = hour_rate

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

class CommissionPolicy(SalaryPolicy):
    def __init__(self, weekly_salary, commission):
        super().__init__(weekly_salary)
        self.commission = commission

    def calculate_payroll(self):
        fixed = super().calculate_payroll()
        return fixed + self.commission
# In employees.py


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

class Manager(Employee, ManagerRole, SalaryPolicy):
    def __init__(self, id, name, weekly_salary):
        SalaryPolicy.__init__(self, weekly_salary)
        Employee.__init__(self, id, name)

class Secretary(Employee, SecretaryRole, SalaryPolicy):
    def __init__(self, id, name, weekly_salary):
        SalaryPolicy.__init__(self, weekly_salary)
        super().__init__(id, name)

class SalesPerson(Employee, SalesRole, CommissionPolicy):
    def __init__(self, id, name, weekly_salary, commission):
        CommissionPolicy.__init__(self, weekly_salary, commission)
        super().__init__(id, name)

class FactoryWorker(Employee, FactoryRole, HourlyPolicy):
    def __init__(self, id, name, hours_worked, hour_rate):
        HourlyPolicy.__init__(self, hours_worked, hour_rate)
        super().__init__(id, name)

class TemporarySecretary(Employee, SecretaryRole, HourlyPolicy):
    def __init__(self, id, name, hours_worked, hour_rate):
        HourlyPolicy.__init__(self, hours_worked, hour_rate)
        super().__init__(id, name)
manager = Manager(1, '<NAME>', 3000)
secretary = Secretary(2, '<NAME>', 1500)
sales_guy = SalesPerson(3, '<NAME>', 1000, 250)
factory_worker = FactoryWorker(4, '<NAME>', 40, 15)
temporary_secretary = TemporarySecretary(5, '<NAME>', 40, 9)

company_employees = [
    manager,
    secretary,
    sales_guy,
    factory_worker,
    temporary_secretary,
]

productivity_system = ProductivitySystem()
productivity_system.track(company_employees, 40)

payroll_system = PayrollSystem()
payroll_system.calculate_payroll(company_employees)<jupyter_output>Tracking Employee Productivity
==============================
<NAME>: screams and yells for 40 hours.
<NAME>: expends 40 hours doing office paperwork.
<NAME>: expends 40 hours on the phone.
<NAME>: manufactures gadgets for 40 hours.
<NAME>: expends 40 hours doing office paperwork.

Calculating Payroll
===================
Payroll for: 1 - <NAME>
- Check amount: 3000

Payroll for: 2 - <NAME>
- Check amount: 1500

Payroll for: 3 - <NAME>
- Check amount: 1250

Payroll for: 4 - <NAME>
- Check amount: 600

Payroll for: 5 - <NAME>
- Check amount: 360<jupyter_text>The employees module imports policies and roles from the other modules and implements the different Employee types. You are still using multiple inheritance to inherit the implementation of the salary policy classes and the productivity roles, but the implementation of each class only needs to deal with initialization.The diagram shows the relationships to define the Secretary and TemporarySecretary using multiple inheritance, but avoiding the diamond problem.![](./img/mi_policies.jpg)  Composition Composition is an object oriented design concept that models a has a relationship. In composition, a class known as composite contains an object of another class known to as component. In other words, a composite class has a component of another class.Composition allows composite classes to reuse the implementation of the components it contains. The composite class doesn’t inherit the component class interface, but it can leverage its implementation.The composition relation between two classes is considered loosely coupled. That means that changes to the component class rarely affect the composite class, and changes to the composite class never affect the component class.This provides better adaptability to change and allows applications to introduce new requirements without affecting existing code.When looking at two competing software designs, one based on inheritance and another based on composition, the composition solution usually is the most flexible. You can now look at how composition works.You’ve already used composition in our examples. If you look at the Employee class, you’ll see that it contains two attributes:id to identify an employee.name to contain the name of the employee.These two attributes are objects that the Employee class has. Therefore, you can say that an Employee has an id and has a name.Another attribute for an Employee might be an Address:<jupyter_code># In contacts.py

class Address:
    def __init__(self, street, city, state, zipcode, street2=''):
        self.street = street
        self.street2 = street2
        self.city = city
        self.state = state
        self.zipcode = zipcode

    def __str__(self):
        lines = [self.street]
        if self.street2:
            lines.append(self.street2)
        lines.append(f'{self.city}, {self.state} {self.zipcode}')
        return '\n'.join(lines)
address = Address('55 Main St.', 'Concord', 'NH', '03301')
print(address)<jupyter_output>55 Main St.
Concord, NH 03301
{'fraction_non_alphanumeric': 0.09158063367746529, 'fraction_numerical': 0.014818440726237095, 'mean_word_length': 4.485233097388333, 'pattern_counts': {'":': 0, '<': 96, '<?xml version=': 0, '>': 96, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '593561', 'license': 'MIT', 'n_tokens_mistral': 6606, 'n_tokens_neox': 6035, 'n_words': 2316, 'path': 'notes.ipynb', 'repo_name': 'cormac-rynne/inheritance_vs_composition', 'size': '22756'}