Python: Наследование по сравнению с композицией

Я работаю с двумя классами в Python, одним из которых должно быть разрешено иметь какие-либо объекты из другого класса в качестве дочерних, сохраняя при этом инвентаризацию этих детей в качестве атрибута. Наследование казалось очевидным выбором для этой родительской < > детской ситуации, но вместо этого я стал примером композиции. Вот упрощенный код:


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

Итак, в основном, хотя кажется, что интуитивно понятно, что Child() наследует от Parent(), удаляя наследование, ничего не меняет. Единственное преимущество, которое я могу видеть для выхода из Child (Parent), а не только для класса Child(), было бы, если бы мне нужно было добавить намного больше методов для родителя, чтобы я хотел, чтобы Child наследовал. Используя self.parent = parent, у меня уже есть доступ к любым дополнительным атрибутам будущего родителя.

Есть ли другой способ использовать чистое наследование, а не передавать родительский экземпляр в конструктор (композицию) Child?

Ответ 1

Определенно нехорошо наследовать ребенка от родителя или родителя от ребенка.

Правильный способ сделать это - создать базовый класс, скажем, Person, и наследовать от него и Child, и Parent. Преимущество этого состоит в том, чтобы удалить повторение кода, на данный момент у вас есть только поля имени и фамилии, скопированные в оба объекта, но у вас может быть больше данных или дополнительных методов, таких как get_name() для работы с этими данными.

Вот пример:

class Person(object):

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

    def get_name(self):
        return '%s %s' % (self.firstname, self.lastname)


class Parent(Person):

    def __init__(self, firstname, lastname):
        super(Parent, self).__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(Child, self).__init__(firstname, parent.lastname)
        self.parent = parent

Другой способ сделать это - сделать это без наследования, но иметь только один объект Person (против Parent и Child). Функция отслеживания статуса семьи и родителей/детей может быть перенесена в другой объект.

Преимущество этого подхода заключается в том, что вы следуете принципу единой ответственности и делаете объекты простыми, каждый объект выполняет только одно.

Вот пример:

from collections import defaultdict

class Person(object):

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

    def get_name(self):
        return '%s %s' % (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 '%s has no children' % person.get_name()
            return
        for child in children:
            print child.get_name()

Это работает так:

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

Ответ 2

Во-первых, я думаю, вы путаете вещи. Как вы сами упоминаете, наследование используется в классе, который хочет наследовать природу родительского класса, а затем модифицировать это поведение и расширять его.

В вашем примере Child наследует две вещи от родителя. Конструктор __init__ и havechild. Вы переопределяете конструктор, а метод havechild не должен работать, поскольку список дочерних элементов для детей не добавляется. Также кажется, что вы не собираетесь иметь детей, у которых есть дети.

Говоря, кажется, вы действительно можете задать другой вопрос, например, "Композиция против агрегирования". Существует такой выбор дизайна, как Inheritance vs Composition, который на самом деле может быть особенно интересен для Python, но в основном он спрашивает, хотите ли вы повторно использовать код, скопировав поведение автономного родительского класса (наследование) или хотите отделить разные графы поведения класса (из-за отсутствия лучшего слова), а затем создавать классы, которые являются составами этих гранул.

взгляните на это! В книге ссылка также хорошо известен и имеет хороший каталог для разных шаблонов дизайна.