anekix
anekix

Reputation: 2563

a realistic usage of composition in python

i have been reading about composition in python and after following some articles and answers here on stackoverlow i think i am able to get what it is and how it is implemented. but one question to which i am unable to find answer is why composition? (not comparing with benefits over inheritance). here is an example that i got from here:

class Salary:
    def __init__(self,pay):
        self.pay=pay

    def get_total(self):
       return (self.pay*12)

class Employee:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus
        self.obj_salary=Salary(self.pay)

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)


obj_emp=Employee(100,10)
print (obj_emp.annual_salary())

i would like to understand with a little realistic example where it would benefit by seperating two related classes( and what benefits exactly?)

Upvotes: 4

Views: 6253

Answers (3)

brian d foy
brian d foy

Reputation: 132858

The trick is to figure out what should have responsibility for what and how you should manage things. People tend to distinguish composition and inheritance as "has a" and "is a". An employee has a salary, but an employee is not a salary. As such, an employee object would have a salary object. That salary object would handle all the things the deal with salary:

class Ledger:
    def __init__(self):
        self.items = [];

    def add_pay(self, amount):
        self.items.append(amount)
        return self

    def total(self):
        return sum(self.items)

class Salary:
    def __init__(self,per_annum,periods):
        self.per_annum = per_annum
        self.periods = periods

    def get_pay(self):
        return self.per_annum / self.periods

class Employee:
    def __init__(self,salary):
        self.salary = salary
        self.ledger = Ledger()


salary = Salary(1200, 24)
employee = Employee( salary )
print( employee.salary.get_pay() )

employee.ledger.add_pay( employee.salary.get_pay() )
print(employee.ledger.total())

employee.ledger.add_pay( 37 )
print(employee.ledger.total())

Likewise, a salary has nothing to do with transactions. So an employee can have a ledger that keeps tracks of payments. This takes care of the bonus problem. A bonus is not salary, so salary shouldn't take responsibility for it. And, a bonus is not an employee, but an employee can have a ledger. Every time the employee gets money, just add it to the ledger.

But, this doesn't mean that you should use this example. It's just one demonstration of one object containing other objects that handle the work. You aren't trying to model how the real world works but figuring out what pieces your code needs to manage and how to effectively do that. This often means the various components that you need to use (database, whatever) sometimes decide how you best structure things.

Upvotes: 0

mad.meesh
mad.meesh

Reputation: 2766

ok i'll give it a try even though i'm kind of a noob. also there may be some points i'm missing but this is the way i like to think about it, any who, the answer:

because it gives you a lot of agility and allows you to 'compose' objects together that may not necessarily have a parent/child relationship (what in classical OOP is referred to as a IS-A type inheritance)

for example say you have a rifleclass. you want to add (or compose) attachments onto it.

how would you do this with inheritance? it wouldn't be very flexible.

so what you could do is define your attachments as there own classes, say a sight and a muzzle break class, and then declare instances of these attachment classes as fields in your rifle class and BAM you now have access to the methods defined in sight and muzzle break, just as when with inheritance a child class has access to its superclasses methods.

anyway to continue, you can see from this example how easy it would be to now create all sorts of weapon classes, like a howitzer, and if done properly you could declare instances of sight and muzzle break in howitzer.

notice that the relationship hierarchy we've created is completely flat.

to do all this with inheritance, you would define a base class, such as weapon with the same methods defined in sight and muzzle break. you would then crate child classes that branch out to rifle and to howitzer.

now imagine you don't want rifle to have a sight but a scope. you have to go through all these hoops and shenanigans in your base class to achieve this.

well with composition you would simply just create a new class scope (which could actually inherit from sight), remove the instances of sight from rifle and insert a field instance of scope. note that no changes were made to howitzer.

with all that said i still think inheritance is a useful tool to be exploited in the proper situations, just like composition.

Upvotes: -1

Joshua Nixon
Joshua Nixon

Reputation: 1427

using an extended version of your example

class Salary:
    def __init__(self,pay):
        self.pay=pay

    def get_total(self):
       return (self.pay*12)

    def increase(self):
        self.pay *= 1.1


class Employee:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus
        self.obj_salary=Salary(self.pay)

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)


obj_emp=Employee(100,10)
print (obj_emp.annual_salary())

calling employee.salary.increase makes much more sense than employee.increase

Also what if you needed multiple objects? would you inherit one of them or all of them leading to name possible name clashes?

class Game:
    def __init__(self):
        self.screens       = [LoadingScreen, MapScreen]        
        self.player        = Player()
        self.display       = Display()
        self.stats         = Stats(self.display)
        self.screenIndex   = self.player.getScreen()
        self.currentScreen = self.screens[self.screenIndex]()
        self.run()

* EDIT * after looking at this

but on looking it up again i find that increase could be renamed to increaseSalary so employee.increaseSalary() would make sense right?

you could simply move the increase to the employee class but what if you had a Manager class or a Boss class you would need to repeat the same code for each class

class Employee:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)

    def increase_salary(self):
        self.pay *= 1.1


class Manager:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)

    def increase_salary(self):
        self.pay *= 1.1

class Boss:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)

    def increase_salary(self):
        self.pay *= 1.1

OR only once

class Salary:
    def __init__(self,pay):
        self.pay=pay

    def get_total(self):
       return (self.pay*12)

    def increase(self, addition):
        self.pay *= addition

you could also use a class methods to find average, max, min etc a lot easier

Upvotes: 7

Related Questions