👻👻👻 Happy Halloween! 👻👻👻
Today I would like to share my experience with implementing a delegational relationship in python. I came across this problem a few weeks ago when I was working on abstracting out a large chunk of our codebase. Basically, the problem boils down to figuring out how to access a price for something and being able to apply coupons, promotions, and other mechanisms that may affect the price.
Say we have a Ticket class that takes in an integer of the price represented in USD cents.
Now let’s say we want to offer a 10% discount. Do we want to create a subclass that inherits the Ticket class?
Okay. That kind of works. But what if we want to combine different kinds of promotions? What happens if we have another promotion that takes a dollar off the price?
We have to remember that ordering matters here too.
We can take a dollar off the discounted price:
$15.00 x 0.9 – $1.00 = $12.50
or we can take a dollar off the original price and apply the 10% off afterwards:
($15.00 – $1.00) x 0.9 = $12.60
We can quickly figure out that achieving this using simple inheritance is going to be challenging. This was when I was introduced to delegation.
Let’s create a new PercentageDiscountWrapper class that doesn’t inherit the Ticket class but “delegates” tasks to the Ticket class. In short, this means that a PercentageDiscountWrapper object will consist of a Ticket object and will delegate anything that isn’t defined on the PercentageDiscountWrapper class to the Ticket class.
As we can see from isinstance(ticket, Ticket) returning False, a PercentageDiscountWrapper class may behave like it’s subclassing the Ticket class but it technically isn’t.
But wait, what happens when we call ticket.formatted_price?
Well, that’s not what we want… This is something that stumped me at first, but it seems like once we delegate the formatted_price property to the Ticket class, it isn’t aware that
it needs to check the PercentageDiscountWrapper class for the price_cents property.
We found a hack around this by adding a setter method and modifying the instance variable on the Ticket object in the PercentageDiscountWrapper initialization.
We can now have a full implementation like this:
With this implementation, we can always access the price without having to do extra if and else checks to see if we’ve applied any kinds of coupons or promotions.
It still seems a lot more hackish than I would like it to be. If you know of a better or more elegant way to achieve this same behavior, please let me know!
PS: As always, we’re looking for talented people! See http://jana.com/careers for more details.