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