title: When to use association, aggregation, composition and inheritance?
body: <p>I've seen plenty of posts on Stackoverflow explaining the difference between the relationships: associations, aggregation, composition and inheritance, with examples. However, I'm more specifically confused more about the pros and cons of each of these approaches, and when one approach is most effective for the task in hand. This is something I've not been able to really find a good answer on.</p>
  
  <p>Keeping inline with the forum's guidelines, I'm <strong>not</strong> asking for why people might personally prefer using inheritance over composition, for example. I'm specifically interested in any objective benefits/weaknesses in each approach, as strong as that may sound. I.e. does one approach create more readable code than another, or does it have a better run time efficiency etc.</p>
  
  <p>Ideally, if someone could give me some real world examples where these approaches may have succeeded or failed and why, that would be <strong>extremely</strong> useful for developing my and, I hope, others knowledge. </p>
  
  <p>In the interest of ensuring a solid base to work off, I've included examples of each relationship in Python 2. Hopefully this should allow confusion to be avoided, if my understanding of these relationships is not in-fact correct.</p>
  
  <p><strong>Association</strong></p>
  
  <p>Class B has a week association relationship with Class A, as it uses specific attributes from A in the addAllNums method. However, that is the extent of the relationship.  </p>
  
  <pre><code>class A(object):
      def __init__(self, a, b, c):
          self.a = a
          self.b = b
          self.c = c
  
      def addNums():
          self.b + self.c
  
  class B(object):
      def __init__(self, d, e):
          self.d = d
          self.e = e
  
      def addAllNums(self, Ab, Ac):
          x = self.d + self.e + Ab + Ac
          return x
  
  ting = A("yo", 2, 6)
  ling = B(5, 9)
  
  print ling.addAllNums(ting.b, ting.c)
  </code></pre>
  
  <p><strong>Aggregation</strong></p>
  
  <p>Class B forms an aggregation relationship with Class A, as it references an independent A object when initialized, as one of its attributes. Whilst a B object is dependent on A, in the event of B's destruction, A will continue to exist as it is independent of B. </p>
  
  <pre><code>class A(object):
      def __init__(self, a, b, c):
          self.a = a
          self.b = b
          self.c = c
  
      def addNums():
          self.b + self.c
  
  class B(object):
      def __init__(self, d, e, A):
          self.d = d
          self.e = e
          self.A = A
  
      def addAllNums(self):
          x = self.d + self.e + self.A.b + self.A.c
          return x
  
  ting = A("yo", 2, 6)
  ling = B(5, 9, ting)
  
  print ling.addAllNums()
  </code></pre>
  
  <p><strong>Composition</strong></p>
  
  <p>Much like aggregation, however rather than referencing an independent object, B actually initializes an instance of A in it's own constructor as an attribute. If the B object is destroyed then so too is the A object. This is why composition is such a strong relationship.  </p>
  
  <pre><code>class A(object):
      def __init__(self, a, b, c):
          self.a = a
          self.b = b
          self.c = c
  
      def addNums():
          self.b + self.c
  
  class B(object):
      def __init__(self, d, e):
          self.d = d
          self.e = e
          self.A = A("yo", 2, 6)
  
      def addAllNums(self):
          x = self.d + self.e + self.A.b + self.A.c
          return x
  
  ling = B(5, 9)
  
  print ling.addAllNums() 
  </code></pre>
  
  <p>I've decided not to include an example of inheritance as I'm completely happy with it, and I feel that its inclusion may throw the point of the question off at a bit of a tangent. </p>
  
  <p>Regardless, again, what are the pros and cons of the above examples and inheritance (compared to one another).</p>
  
  <p>Thanks. </p>