Locked learning resources

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

Unlock This Lesson

Locked learning resources

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

Unlock This Lesson

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.

Avatar image for Lucas Dondo

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 then new_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 of LTDPolicy. However, if any other policy is passed, an AttributeError is raised since apply_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?

Avatar image for Marcelo Borges

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.