首页 > 解决方案 > 在 Python 中使用组合而不是继承来实现多态性的正确方法

问题描述

在处理一个项目时,我在使用继承时陷入了我的设计中。现在我尝试摆脱它,而是使用组合,因为这似乎是解决我的问题的合适方法。但是,我需要 polymorphisme,我不确定我是否以正确的方式实现了我的构图。

有人可以看看我下面的代码吗?在最后三行中,我希望所有的动物都能走路,但前提是它们有能力走路。在调用此属性的函数之前首先检查对象是否具有某个属性(在本例中为“腿”)是一种好习惯吗?我只是好奇这是否是正确的方法,或者是否有更好的方法。

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()

标签: pythoninheritancepolymorphismcompositionreview

解决方案


实际上你已经有点过头了xD

继承描述“是”关系,组合描述“具有”关系。因此,在您的情况下,对翅膀和腿等属性使用合成非常有意义,但是鸟、猫和狗是动物——它们没有“拥有”动物(好吧,它们都有跳蚤,但这是另一个话题)——所以它们应该继承自Animal.

此外,大多数鸟的腿也太 AFAICT,而且有不少实际上根本不会飞(但有些用它们来游泳,而且这样做非常有效);-)

在调用此属性的函数之前首先检查对象是否具有某个属性(在本例中为“腿”)是一种好习惯吗?

取决于上下文,真的。作为一般规则,不,这不被认为是好的做法(参见“告诉不要问”和“demeter 法则”),但在某些情况下它是合法的。此外,“好的”设计还取决于要解决的问题,我们在这里达到了无法代表现实生活用例的玩具示例的极限。

从理论上讲,组合/委托对客户端代码应该是透明的,因此您应该调用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

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


推荐阅读