Reputation: 1
I'm looking for a design pattern suited to Python that will address the following problem:
Assuming a class Company
that has a member employees
, which is a list that will contain any number of Employee
s.
What I would like to capture is when one of the Employee
's member variables (let's say salary
) changes, that change is reflected in the "owning" Company
(let's say total_salary
). Let's also assume that total_salary
is very expensive to calculate and we only want to do that calculation whenever any employee's salary is changed and not whenever we access it as a property.
> c = Company()
> print(c.total_salary)
0
> c.employees.append(Employee())
> print(c.total_salary)
0
c.employees[0].salary = 100 # update total_salary for c now
> print(c.total_salary)
100
The obvious way is by making sure that there is a reference from each Employee
back to its owning Company
. What I'm curious is whether there is a good way to use a getter, setter or something else in Company
to ensure that I can capture a salary
change to any element in employees
, so that I can immediately update total_salary
. Crucially, we should avoid this recalculation when another member is updated (such as name
).
Upvotes: 0
Views: 147
Reputation: 104752
I think there are a couple of reasonable ways to do what you're asking. One is to have the company dynamically compute its totals each time they're requested. The other approach is for the Employee
to know the company it belongs to, and for it to update the company's totals whenever it's own information changes.
I think the first approach is easier, since the Employee
instances doesn't need to know anything about the Company
, and you don't to do anything special when an Employee is updated. The downside is that if you have a company with many employees and request its totals often, it may be slow, since it needs to iterate over all the employees each time. Here's how I'd implement it, using a property
:
class Company:
def __init__(self):
self.employees = []
@property
def total_salary(self):
return sum(e.salary for e in self.employees)
If you go with the second approach, you'd also use a property
, but you'd put it in the Employee
class, so it can detect changes being made to the salary
attribute:
class Company:
def __init__(self):
self.employees = []
self.total_salary = 0
class Employee:
def __init__(self, employer):
self.employer = employer
self._salary = 0
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, value):
self.employer.total_salary += value - self._salary
self._salary = value
To make this work in a more complicated system, you'd probably want a bunch of other methods, like one that adds an Employee
with a salary already set to a Company
(currently you must add the Employee
to the company first, then update it's salary, or the totals will be wrong).
Upvotes: 0
Reputation: 1230
If you're looking for getter/setter control, look into Python property
. You can use this pattern to catch when variables are set, in order to implement the auto-update feature.
Upvotes: 0
Reputation: 6721
class Company:
def __init__(self, ....):
self.employees = []
self.total_salary = 0
def add_employe(self, employe):
self.employees.append(employe)
self.total_salary += employe.salary
def remove_employe(self, employe):
self.employees.remove(employe)
self.total_salary -= employe.salary
def update_employe(self, employe):
for e in self.employees:
if not employe.id == e.id:
continue
e.name = employe.name
# ... update more data here
if employe.salary != e.salary: # salary suffered changes, update it
self.total_salary -= e.salary # subtract the older
self.total_salary += employe.salary # sum up the new
e.salary = employe.salary # update it
class Employee:
_id = itertools.count(start=1)
def __init__(self, ...):
self.id = next(Employee._id)
# ...
You want update total_salary
only if the employe.salary
suffered any changes. The responsible for this is in update_employe
method.
Also an implementation of some sort of id
to Employee
is useful.
Upvotes: 1