Avoiding the Diamond Problem
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 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.
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
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.
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
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.
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.
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.
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.
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.
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.
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.
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
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.
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
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
All that’s left to do is initialize the
Employee base class, which we can do with
super() will follow the MRO, but the first place it will look is
Employee since we inherited from that class first.
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
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: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.
Become a Member to join the conversation.