Composition to Change Runtime Behavior
00:00 You saw before that inheritance creates a tightly-coupled relationship. If we want to change the behavior in the derived class, we must override a method inherited from the base class.
00:14 This is helpful in certain circumstances, but it creates rigid designs that are hard to change moving forward. Composition, on the other hand, creates a loosely-coupled relationship that enables flexible designs and can be used to change behavior at run-time.
00:34 Let’s say that requirements change and now we need to support a longterm disability policy, or LTD policy, for short. This policy states that an employee on LTD should be paid 60% of their weekly salary, assuming 40 hours per week.
00:56
Adding this to our current design won’t be too hard. I’m going to start by entering hr.py
and in here, I’ll create a new class called LTDPolicy
.
01:10
This class will expose the same interface as PayrollPolicy
, but I don’t want to inherit its method implementations, so I’m not going to inherit from it. It will look similar, though.
01:25
First, I will initialize it with a ._base_policy
, which will start as None
type. This ._base_policy
is the policy the employee had before going on LTD.
01:39
The new pay will be 60% of this. To follow the interface in the PayrollPolicy
, we also need a .track_work()
method, which will accept the number of hours to work and then track employees for that time.
01:57
This ._check_base_policy()
is a method we’ll implement in a moment, which will check to make sure that the ._base_policy
attribute is not None
.
02:08
If it isn’t, execution will continue, but if it is None
, an exception will be raised. If everything checks out, we’ll track the work using the ._base_policy
.
02:22
We’ll follow similar steps for the .calculate_payroll()
method. We’ll check the ._base_policy
, and if that checks out okay, we’ll get the employee’s normal base_salary
and return 60% of that. Because I marked the ._base_policy
instance attribute as private, I’ll create a method that we can use to set a new base policy.
02:50
All that’s left to do now is implement the ._check_base_policy()
method, which will raise a RuntimeError
in the event that a base policy is missing.
03:02
Now we can utilize this new class by adding just one more method to the Employee
class. I’ll move over to employee.py
and at the bottom of that class, I will create a method called .apply_payroll_policy()
.
03:21
This method will take in a new payroll policy to apply, which will be the LTDPolicy
, and then store the existing policy within that new_policy
object.
03:36
Then, it’ll set the current ._payroll
value to the new_policy
. Finally, we can modify the main program
module to try this new feature out.
03:49
I’m going to delete everything except our imports and the employees
list. I want to modify the third employee in the list to give him this new LTDPolicy
.
04:03
The third employee in the database, the sales employee, can be accessed at the second index in the employees
list.
04:13 Now that we’ve got that employee, let’s create the longterm disability policy and apply it to them.
04:24
They now have a longterm disability policy applied to them, which itself remembers their old payroll policy and uses that to calculate their new salary. And before I run this, I’m going to make sure to import this new class from the hr
module so we don’t see any more exceptions in this course.
04:52
Notice now that our sales employee, Kevin Bacon
—with the coolest name ever—previously made $1,800, but now he only makes 60% of that. As you can see, you were able to support this change just by adding a new policy and modifying a couple of interfaces.
05:13 This is the kind of flexibility that policy-based design gives you.
Marcelo Borges on June 23, 2023
Confusion lies in the fact that the apply_payroll_policy
method name and signature leads the viewer into believing the method can take instances of PayrollPolicy
, but it does not. The lesson should have made it clearer that the new_policy
method parameter expects not an instance of PayrollPolicy
, but an instance of LTDPolicy
that extends the PayrollPolicy
interface.
Become a Member to join the conversation.
Lucas Dondo on May 22, 2023
I understand the idea and everything, but there’s something about the code that I don’t quite get why it has been done that way. In the
apply_payroll_policy
method,new_policy
is passed as a parameter, and thennew_policy.apply_to_policy(self._payroll)
gets executed.I understand the concept behind and how this works. And it actually works if
new_policy
is an instance ofLTDPolicy
. However, if any other policy is passed, anAttributeError
is raised sinceapply_to_policy
is not a method of all policies.Shouldn’t, then, part of this code be inside a
try-except
block? Or, at least, doing so would improve code’s reliability, right?