Правильный способ иметь полиморфизм с композицией вместо наследования в Python - PullRequest
0 голосов
/ 01 февраля 2019

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

Может кто-нибудь взглянуть на мой код ниже?В последних трех строках я хочу, чтобы все животные гуляли, но только если они способны ходить.Полезно ли сначала проверять, имеет ли объект определенный атрибут (в данном случае «ноги»), прежде чем вызывать функцию с этим атрибутом?Мне просто любопытно, если это правильный способ сделать это или есть лучший способ.

class Animal:

    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print("silence...")


class Wings:

    def flap(self):
        print("Wings are flapping")


class Legs:

    def walk(self):
        print("Legs are walking")


class Bird:

    def __init__(self):
        self.animal = Animal("Bird")
        self.wings = Wings()

    def make_sound(self):
        print(f"{self.animal.name} is Singing!")


class Dog:

    def __init__(self):
        self.animal = Animal("Dog")
        self.legs = Legs()

    def make_sound(self):
        print(f"{self.animal.name} is Barking")


class Cat:

    def __init__(self):
        self.animal = Animal("Cat")
        self.legs = Legs()

    def make_sound(self):
        print(f"{self.animal.name} is Meowing!")


if __name__ == '__main__':

    animals = list()

    animals.append(Bird())
    animals.append(Dog())
    animals.append(Cat())

    for animal in animals:
        animal.make_sound()

    for animal in animals:
        if hasattr(animal, 'legs'):
            animal.legs.walk()

1 Ответ

0 голосов
/ 01 февраля 2019

На самом деле вы немного превзошли xD

Наследование описывает отношение "есть", композиция описывает отношение "имеет".Так что в вашем случае использование композиции для таких атрибутов, как крылья и ноги, имеет смысл, но птицы, кошки и собаки - это животные - у них нет «животных» (ну, у них всех есть блохи, но это уже другая тема), поэтому онидолжен наследовать от Animal.

Кроме того, у большинства птиц ноги слишком сильные, и многие из них вообще не летают вообще (но некоторые используют их, чтобы плавать, и делают это очень эффективно) ;-)

Рекомендуется ли сначала проверять, имеет ли объект определенный атрибут (в данном случае «ноги»), прежде чем вызывать функцию с этим атрибутом?

Зависит отконтекст, действительно.Как правило, нет, это не считается хорошей практикой (см. «Говори, не спрашивай» и «закон деметры»), но есть случаи, когда это законно.Кроме того, «хороший» дизайн также зависит от решаемой проблемы, и здесь мы достигаем предела примеров игрушек, которые никогда не являются реальными примерами использования.

Теоретически состав / делегирование должно быть прозрачным длякод клиента, поэтому вам нужно просто позвонить whatever_animal.walk() и покончить с этим.Теперь вы (как «код клиента»), возможно, захотите узнать, что животное не может ходить, и в этом случае не ходячее животное должно вызывать исключение, когда ему говорят: «Это также означает, что Animal должна иметь реализацию по умолчаниювсе возможные «действия» и то, что клиентский код должен быть подготовлен для исключений «UnsupportedAction» (или как вы хотите их называть).

по сравнению с реализацией, прозрачность делегирования может быть такой же простой, как и использование __getattr__(), то есть:

class UnsupportedAction(LookupError):
    pass

class Animal(object):
    _attributes = ()

    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print("silence...")

    def __getattr__(self, name):
        for att in self._attributes:
            if hasattr(att, name):
                return getattr(att, name)
        else:
            raise UnsupportedAction("{} doesn't know how to {}".format(type(self), name))



class Dog(Animal):
    _attributes = (Legs(), )


class Bird(Animal):
    _attributes = (Legs(), Wings())

Приятным моментом этого решения является то, что оно очень простое и очень динамичное.Менее приятным моментом является то, что он не является ни проверяемым, ни явным.

Другое решение - явное делегирование:

class UnsupportedAction(LookupError):
    pass

class Animal(object):
    _attributes = ()

    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print("silence...")


    def walk(self):
        return self._resolve_action("walk")

    def fly(self):
        return self._resolve_action("walk")

    # etc            

    def _resolve_action(self, name):
        for att in self._attributes:
            if hasattr(att, name):
                return getattr(att, name)
        else:
            raise UnsupportedAction("{} doesn't know how to {}".format(type(self), name))

, которое является более многословным, гораздо менее динамичным, но гораздо более очевидным, задокументированнымчитаемый и проверяемый.

В приведенном выше примере вы могли бы фактически выделить лишний код с помощью пользовательского дескриптора:

class Action(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, obj, cls):
        if obj is None:
            return self
        return obj._resolve_action(self.name)

    def __set__(self, obj, value):
        raise AttributeError("Attribute is readonly")


class Animal(object):
    _attributes = ()

    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print("silence...")

    walk = Action("walk")
    fly = Action("fly")

    # etc

Но, опять же, ничего из этого не имеет смысла без реальной проблемырешить, что обычно определяет правильное решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...