在Python中用组合而不是继承来实现多态性的正确方法

2024-06-16 11:26:43 发布

您现在位置:Python中文网/ 问答频道 /正文

在一个项目中,我在使用继承的时候陷入了设计中。现在我试着摆脱它,转而使用合成,因为这似乎是解决我问题的合适方法。然而,我需要多愁善感,我不确定我是否以正确的方式实现了我的作品。在

有人能看看我下面的代码吗?在最后三行中,我希望所有的动物都能行走,但前提是它们有行走的能力。在调用这个属性的函数之前,先检查一个对象是否具有某个属性(在本例中为“legs”),这是一个好的做法吗?我只是想知道这是一个正确的方法,还是有更好的方法。在

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

Tags: 方法nameselfmakeinitdefclassprint
1条回答
网友
1楼 · 发布于 2024-06-16 11:26:43

你已经有点过头了

继承描述“是”关系,组合描述“有”关系。因此,在您的例子中,使用组合来处理像翅膀和腿这样的属性是非常合理的,但是鸟、猫和狗都是动物-它们没有动物(嗯,它们都有跳蚤,但这是另一个主题)-所以它们应该继承自Animal。在

而且,大多数鸟的腿太粗了,还有相当一部分根本就不会飞(但有些鸟用腿游泳,而且游得很有效率);—)

Is it good practice to first check if an object has a certain attribute (in this case 'legs') before calling a function on this attribute?

要看具体情况。一般来说,不,这不被认为是好的做法(参见“告诉-不要问”和“德米特法则”),但在某些情况下它是合法的。而且,“好”的设计也取决于要解决的问题,我们这里达到了玩具示例的极限,这些示例永远不能代表真实的用例。在

理论上,组合/委托对客户机代码应该是透明的,所以您应该只调用whatever_animal.walk()并完成它。现在你(作为“客户端代码”)可能想知道动物不能行走,在这种情况下,非行走的动物应该提出一个例外,当收费步行。。。它还意味着Animal必须为所有可能的“actions”提供一个默认实现,并且客户机代码必须为“UnsupportedAction”(或者您想如何命名它们)异常做好准备。在

wrt/implementation,使委托透明可以像使用__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())

这个解决方案的优点是它非常简单而且非常动态。不太好的一点是它既不可检查也不明确。在

另一种解决方案是明确授权:

^{pr2}$

这是相当冗长,更不动态,但许多移动明显,记录,可读和检查。在

在上面的示例中,您实际上可以使用自定义描述符来分解冗余代码:

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

但是,再一次,如果没有一个真正的问题需要解决,那么这些都是没有意义的,因为这个问题通常定义了正确的解决方案。在

相关问题 更多 >