在Python中具有合成而不是继承的多态的正确方法

时间:2019-02-01 13:12:00

标签: python inheritance polymorphism composition review

在处理项目时,我在使用继承时陷入了设计困境。现在,我试图摆脱它,而是使用组成,因为这似乎是适当的解决我的问题。不过,我需要polymorfisme,我不知道如果我实现了我的作文以正确的方式。

有人可以看下面我的代码吗?在最后三行我希望所有的动物行走,但前提是他们必须走的能力。优良作法是在对该属性调用函数之前先检查对象是否具有特定属性(在这种情况下为“腿”)?我只是想知道这是执行此操作的正确方法,还是有更好的方法。

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 :(得分:1)

您实际上已经超出了xD的顶部

继承描述了“是”的关系,组合物描述了一种“具有”的关系。所以你的情况,使用组成像翅膀和腿的属性非常有意义,但鸟,猫与狗是动物 - 它们没有“有”动物(当然,他们都有跳蚤却是另一个话题) - 所以他们应该从Animal继承。

此外,大多数鸟类的腿也都具有AAFICT的特质,而且实际上有很多鸟根本不会飞(但是有些鸟会用它们游泳,而且效率很高);-)

  

它是好的做法是第一检查如果一个对象调用此属性的函数之前具有一定的属性(在这种情况下“腿”)?

取决于上下文,真的。作为一般规则,不,这不是好的做法(参见“告诉不问”和“得墨忒耳定律”),但也有它的合理的情况。此外,“好”的设计也依赖于解决问题,而我们到达这里的玩具例子是从来没有代表现实生活中的使用情况的限制。

理论上,组合物/委派应该是透明的客户端代码,所以你应该只是调用whatever_animal.walk(),并用它来完成。现在,您(作为“客户端代码”)可能想知道动物不能走路,在这种情况下,非走路动物应该在被告知走路时引发异常……这也意味着Animal必须具有默认值实施,以及客户端代码必须为“UnsupportedAction”准备所有可能的“行动”(或但是你想给它们命名)例外。

wrt /实现,使委派透明,可以像使用__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

但是再说一次,没有一个真正要解决的问题(通常定义了正确的解决方案),这一切都不有意义。