Larry
Larry

Reputation: 1

Recommended Python design for capturing changes to class members

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 Employees.

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

Answers (3)

Blckknght
Blckknght

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

Niayesh Isky
Niayesh Isky

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

R.R.C.
R.R.C.

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

Related Questions