Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Avoiding the Diamond Problem

Give Feedback

00:00 Our project is still fairly small, so it won’t be a big hassle to redesign it to avoid the diamond problem. Here’s how this is going to work. We have four modules: program, which is the actual script we run, as well as hr, productivity, and employees.

00:21 hr is going to contain all the policies that are used for the PayrollSystem, like hourly, salary, or commission. productivity will define roles that determine how employees are tracked in the productivity system, like manager, secretary, or salesperson.

00:42 The employees module will define a class for each type of employee, leveraging multiple inheritance. Each class will inherit a payroll policy to be used in the PayrollSystem and a role to be used in the ProductivitySystem.

01:00 What’s cool about this is that our role and policy classes will not explicitly inherit from anything else. This means that when we instantiate our employee classes, each of which relies on multiple inheritance, we won’t have to go digging through the MRO to figure out what’s going on under the hood. I think we can all agree that’s a good thing.

01:24 This UML diagram on the right represents a portion of this redesign. Specifically, this shows the design of the Secretary and TemporarySecretary classes.

01:37 It looks like it’s more complicated at first, but if you look at it carefully, you can see that it avoids the diamond problem.

01:47 We have our base Employee class and a Secretary and a TemporarySecretary that inherit from it. The Secretary also inherits a SalaryPolicy that defines how its payroll is calculated and a SecretaryRole that defines how its hours are tracked in the ProductivitySystem.

02:10 You can see the TemporarySecretary also inherits the SecretaryRole, but instead of inheriting the SalaryPolicy, it inherits the HourlyPolicy.

02:22 The interfaces show us that the role class conforms to the ProductivitySystem and the policy classes conform to the PayrollSystem.

02:33 What we’ve done here is utilize multiple inheritance to decouple these various classes from the various systems they must conform to. All the code relating to one system will live in a single module, which also makes things easy to find.

02:51 Let’s see how this looks in code. I am here in Visual Studio Code in program.py, and one thing I want to point out here is that this is basically going to be a drop-in replacement of our other modules.

03:07 What I mean by that is that after we modify the other modules, we won’t have to change the main program module at all. Everything will still just work because we’re not actually changing the underlying interfaces.

03:24 I’m going to start work in the productivity module, which currently just contains the ProductivitySystem. What we want to do is define a policy for each type of employee that defines how they actually work. Let’s start with the manager.

03:43 This class will be called ManagerRole, and it won’t inherit from anything. It will just define a single method .work() that will look similar to before.

03:56 And now, through the magic of editing, I will define the other three role classes that work in the exact same way.

04:06 Notice that instead of having the .work() method print something to the screen directly, we’re just returning a string. That’s fine, but that means that we have to modify the ProductivitySystem to account for this change. Fortunately for us, that’s right up here at the top.

04:28 I’ll delete the body of this for loop and then store the return value of calling each employee’s .work() method in this variable called result.

04:41 And now I’ll simply print the f-string f"{employee.name}: {result}", and that’s it for the ProductivitySystem. Next, let’s attack the HR system.

04:55 This time, we won’t modify the PayrollSystem at all but we will add some new classes that define policies it will use. I’ll start with the SalaryPolicy, which will be inherited by an employee with a salary.

05:13 Just like with the ProductivitySystem, this will not inherit from any other classes. The .__init__() method will simply initialize an instance attribute for the weekly salary. For .calculate_payroll(), we will simply return this instance attribute.

05:33 I’ve gone ahead and created the other two policy classes. This is all familiar code, just in a new place. Remember that CommissionPolicy inherits from SalaryEmployee because someone who is paid a commission also receives a weekly salary. As such, we use the super() function to get the salary amount and add it to the commissions amount in the .calculate_payroll() method.

06:03 The last module we need to work on is employees.py. This is going to require some big changes, so I think it’s easiest if we just nuke it and start fresh.

06:16 The first thing I want to do is import the new classes we created from our other modules. We should be able to get away with using import * here, but for good practice, I’ll list the classes we want to import manually.

06:33 I’ll say from hr import (SalaryPolicy, CommissionPolicy, HourlyPolicy), from productivity import (ManagerRole, SecretaryRole, SalesRole, FactoryRole).

06:55 Now, I’m going to recreate the Employee class.

07:01 This will look the exact same as it did before.

07:06 Next, we are going to create the specialized employee classes. Each one of these classes will inherit first from Employee, then from a productivity role class, then from a payroll policy class.

07:23 Let’s start with the Manager. The manager is an employee who gets tracked as a manager and paid a salary. They aren’t paid hourly, so it doesn’t make sense to give them hours_worked or hour_rate arguments.

07:43 We just want to make sure the Manager has a .weekly_salary attribute and a .calculate_payroll() method, so I’m going to call the SalaryPolicy class’s .__init__() method.

07:57 As for the .work() method, that will automatically be inherited from the ManagerRole class, and we don’t have to initialize anything because that .work() method doesn’t rely on any instance attributes—just an hours argument.

08:14 All that’s left to do is initialize the Employee base class, which we can do with super(). super() will follow the MRO, but the first place it will look is Employee since we inherited from that class first.

08:32 The signature for this .__init__() method matches that of the .__init__() method in Employee,

08:39 and that method doesn’t call super(), so it will stop there. Our Manager class is now created.

08:48 The Secretary class looks the exact same, except they inherit from SecretaryRole instead of ManagerRole. These last three classes are also very similar.

09:01 You might notice a pattern here within the .__init__() method. Initialize the payroll policy to obtain the payroll instance attributes needed by our inherited .calculate_payroll() method, then call super() to initialize the Employee base class, giving this class a name and an id.

09:24 Now that the redesign is complete, I’m going to move back over to program.py, and if all is working, we should be able to run this without modifying any of these object instantiations. That’s because we redesigned these classes but we kept their interfaces the same.

09:46 The .__init__() methods for each employee type still requires the same list of arguments as before. I will run this, and we see that everything still works.

09:59 It might seem like we did a whole lot of work for nothing, but by redesigning the software, we’ve now avoided the diamond problem and we’ve ensured that it’ll be easy to create new employee types, roles, and payroll policies in the future, without having to dig through the MRO to figure out what kind of new classes we are creating. In the next video, you’ll see how we can extend this program’s functionality with composition.

dwalsh on May 27, 2020

Really great video. Once I got through the Multiple Inheritance video about 5 or 6 times to understand the intricacies, this made way more sense and is so much cleaner in terms of inheritance.

Become a Member to join the conversation.