composition and aggregation in python

Composition and aggregation are specialised form of Association. Whereas Association is a relationship between two classes without any rules.

Composition

In composition, one of the classes is composed of one or more instance of other classes. In other words, one class is container and other class is content and if you delete the container object then all of its contents objects are also deleted.

Now let's see an example of composition in Python 3.5. Class Employee is container and class Salary is content.

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())

Aggregation

Aggregation is a weak form of composition. If you delete the container object contents objects can live without container object.

Now let's see an example of aggregation in Python 3.5. Again Class Employee is container and class Salary is content.

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

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


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

If I understand correctly, aggregation vs composition is about the responsibilities of an object to its members (e.g. if you delete an instance, do you also delete its members?).

Mainly, it will depend a lot on the implementation. For example, to create a class A which receives an instance of class B (aggregation), you could write the following:

class B(object): pass

class A(object):
    def __init__(self, b):
        self.b = b

b = B()
a = A(b)

But as a point of caution, there is nothing built-in to Python that will prevent you from passing in something else, for example:

a = A("string") # still valid

If you would like to create the instance of B inside the constructor of A (composition), you could write the following:

class A(object):
    def __init__(self):
        self.b = B()

Or, you could inject the class into the constructor, and then create an instance, like so:

class A(object):
    def __init__(self, B):
        self.b = B()

As an aside, in at least your first example and possibly the second, you are setting B to the class definition of B, not to an instance of it:

class A(object):
    def __init__(self, B):
        self.B = B

>>> a = A()
>>> a.B # class definition
<class __main__.B at 0x028586C0>
>>> a.B() # which you can make instances of
<__main__.B instance at 0x02860990>

So, you end up with an instance of A pointing to the class definition of B, which I'm fairly sure is not what you're after. Although, that is generally much harder to do in other languages, so I understand if that was one of the points of confusion.


# Aggregation is NOT exclusive
class BaseChapter:
    '''
    We can use this BaseChapter in any book, like in OpenBook.
    '''

    def __init__(self, name):
        self.name = name
        self.subject = None
        self.content = None
        return

class OpenBook:

    def __init__(self, isbn):
        self.isbn = isbn
        self.chapters = list()

    def add_chapter(self, obj):

        # This constrain dont have correlation with composition/aggregation
        if isinstance(obj, BaseChapter):
            self.chapters.append(obj)
        else:
            raise TypeError('ChapterError')

# .. but Composition is Exclusive
# Example:
class MyBook:

    class MyChapter:
        '''
        This MyChapter can be used only by MyBook
        '''
        def __init__(self, name, subject):
            self.name = name
            self.subject = subject
            self.title = None
            self.content = None
            self.techincal_refs = list()
            return

    def __init__(self, isbn):
        self.isbn = isbn
        self.chapters = list()

    def add_chapter(self, obj):
        # This constrain dont have correlation with composition/aggregation
        # what is important here is MyChapter can be used only by MyBook
        # a outside object cant create a instance of MyChapter
        if isinstance(obj, self.MyChapter):
            self.chapters.append(obj)
        else:
            raise TypeError('ChapterError')

.. and yes we can do better like

class MyBook:

    class MyChapter(BaseChapter):
        '''
        This MyChapter can be used only by MyBook,
        but now is based in BaseChapter.
        But you knhow, python dont create problems if you still want
        create a instance of MyChapter in other 'Books'.

        But when you see this code you will think, This class is exclusive
        to MyBook.
        '''
        def __init__(self, name):
            super().__init__(name)
            self.subject = None
            self.title = None
            self.content = None
            self.techincal_refs = list()
            return

    def __init__(self, nib):
        self.nib = nib
        self.chapters = list()

    def add_chapter(self, obj):
        # This constrain dont have correlation with composition/agregation
        # what is important here is MyChapter can be used only by MyBook
        if isinstance(obj, self.MyChapter):
            self.chapters.append(obj)
        else:
            raise TypeError('ChapterError')

Tags:

Python