Python: Inheritance versus Composition

17,133

Solution 1

It is definitely not good to inherint Child from Parent or Parent from Child.

The correct way to do it is to make a base class, let's say Person and inherit both Child and Parent from it. An advantage of doing this is to remove code repetition, at the moment you have only firstname / lastname fields copied into both objects, but you may have more data or additional methods, like get_name() to work with this data.

Here is an example:

class Person:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def get_name(self):
        return f"{self.firstname} {self.lastname}"


class Parent(Person):
    def __init__(self, firstname, lastname):
        super().__init__(firstname, lastname)
        self.kids = []

    def havechild(self, firstname):
        print(self.firstname, "is having a child")
        self.kids.append(Child(self, firstname))


class Child(Person):
    def __init__(self, parent, firstname):
        super().__init__(firstname, parent.lastname)
        self.parent = parent

Another way of doing this is to do it without inheritance, but only have one Person object (vs Parent and Child). The feature of tracking family status and parents / children can be moved into another object.

An advantage of this approach is that you follow the single responsibility principle and keep objects simple, each object does only one thing.

Here is an example:

from collections import defaultdict


class Person:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def get_name(self):
        return f"{self.firstname} {self.lastname}"


class FamilyRegistry(object):
    def __init__(self):
        self.kids = defaultdict(list)

    def register_birth(self, parent, child_name):
        print(parent.firstname, "is having a child")
        child = Person(child_name, parent.lastname)
        self.kids[parent.lastname].append(child)
        return child

    def print_children(self, person):
        children = self.kids[person.lastname]
        if len(children) == 0:
            print("{} has no children" % person.get_name())
            return
        for child in children:
            print(child.get_name())

It works like this:

joe = Person('Joe', 'Black')
jill = Person('Jill', 'White')
registry = FamilyRegistry()
registry.register_birth(joe, 'Joe Junior') # Joe is having a child
registry.register_birth(joe, 'Tina')       # Joe is having a child
registry.print_children(joe)               # Joe Junior Black
                                           # Tina Black
registry.print_children(jill)              # Jill White has no children

Solution 2

First off, I think you are confusing things. As you yourself mention inheritance is used in a class that wants to inherit the nature of the parent class and then modify that behavior and extend it.

In your example Child inherits two things from Parent. The constructor __init__ and havechild. You are overriding the constructor and the havechild method shouldn't work, since there is no kids member list to append the new child to. Also it appears that you are not intending to have the children have children.

That being said it seems you may actually want to ask a different question, such as Composition vs Aggregation. There is such a design choice as Inheritance vs Composition which actually may be particularly interesting for Python, but it mainly asks whether you want to reuse code by copying the behavior of a standalone parent class (inheritance) or you want to separate different class behavior granules (for lack of better word) and then create classes which are compositions of these granules.

have a look at this! The book referenced is also well known and has a good catalogue for different design patterns.

Share:
17,133

Related videos on Youtube

Arne
Author by

Arne

WannaBe Pythonista

Updated on February 13, 2021

Comments

  • Arne
    Arne over 3 years

    I am working with two classes in Python, one of which should be allowed to have any number objects from another class as children while keeping an inventory of these children as an attribute. Inheritance seemed like the obvious choice for this parent<>child situation but instead what I have arrived at is an example of composition. Here is the simplified code:

    class Parent:
        def __init__(self, firstname, lastname):
            self.firstname = firstname
            self.lastname = lastname
            self.kids = []
    
        def havechild(self, firstname):
            print(self.firstname, "is having a child")
            self.kids.append(Child(self, firstname))
    
    
    class Child(Parent):
        def __init__(self, parent, firstname):
            self.parent = parent
            self.firstname = firstname
            self.lastname = parent.lastname
    

    So basically, while it seems to make intuitive sense to have Child() inherit from Parent(), removing the inheritance, does not change anything at all. The only benefit I can see for leaving Child(Parent) instead of just class Child() would be if I needed to add a lot more methods to Parent that I would like Child to inherit. Using the self.parent = parent, I already have access to any additional future attributes of the Parent.

    Is there another way to use pure inheritance rather than passing the Parent instance into the Child constructor (composition)?

    • wheaties
      wheaties over 10 years
      No, you did it the right way. It would be bad to introduce a coupling between objects via inheritance just to share methods between them unless you wanted them to have an "is a" and even then, you might want to go the "mixin" route instead.
    • hankd
      hankd over 10 years
      Inheritance is for "is-a" relationships. Is a child a parent? Not necessarily. Composition is for "has-a" relationships. A child has a parent (and a parent has a child). You would use inheritance if you had a person class, then a child is a person, so child would inherit from person.
    • Ffisegydd
      Ffisegydd over 10 years
      If you needed the two classes to share some common methods then you could always subclass both from a Person class.
    • BartoszKP
      BartoszKP over 10 years
      "Parent" or "Child" are accidental properties that "People" happen to sometimes have or not. So in case of this nomenclature you should have only one class - Person with its kids array empty or not.
    • Robert Jacobs
      Robert Jacobs over 10 years
      You might want inheritence if you want your children to be able to havechild(...).
    • abarnert
      abarnert over 10 years
      As a side note, you almost certainly wanted class Parent(object):, not class Parent():. Otherwise, you're creating an old-style ("classic") class, and they have all kinds of funky rules for inheritance that you really don't want to bother learning.
    • abarnert
      abarnert over 10 years
      Anyway, I think you're just confused here by the ancestry/inheritance relationships between the objects and those of their types. And that's caused by picking a bad example. Most kinds of objects don't have any ancestry issues. Car objects don't have children, so it's not confusing to talk about the SportsCar type being a child type of Car but Wheel not being a child type of Car. There are only a few examples that could have led to this confusion; you just got unlucky.
    • Arne
      Arne over 10 years
      Thanks for the feedback. The most important reminder was to think about the difference between "has-a" and "is-a". The actual problem wasn't about persons (parents or children), and the way I extended the parent/child analogy, it would apply to birds on a tree or files on a shelf. Each time, there is a group of objects that share attributes with the "parent" object, such as e.g. location, owner, age... Basically, I was more concerned with handing down the attribute data from the parent to the child than letting the child inherit behavior. I see a little clearer now.