Py学习  »  Python

在python中使用组合而不是继承的多态性的正确方法

Enzo • 5 年前 • 1585 次点击  

在做一个项目时,我在使用继承时陷入了设计中。现在我试着去掉它,改为使用构图,因为这似乎是解决我问题的合适方法。然而,我需要多动症,我不确定我是否以正确的方式实现了我的作文。

有人能看看我下面的代码吗?在最后三行中,我希望所有的动物都能行走,但前提是它们有能力行走。在对该属性调用函数之前,首先检查对象是否具有某个属性(在本例中为“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()
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/40916
 
1585 次点击  
文章 [ 1 ]  |  最新文章 5 年前
bruno desthuilliers
Reply   •   1 楼
bruno desthuilliers    6 年前

你有点过分了实际上

继承描述的是一种“是”的关系,组合描述的是一种“有”的关系。所以在你的例子中,用构图来表示翅膀和腿之类的属性是非常有意义的,但是鸟、猫和狗都是动物——它们没有“拥有”动物(嗯,它们都有跳蚤,但那是另一个话题)——所以它们应该继承自 Animal .

而且,大多数鸟的腿都太长了,而且有相当一部分实际上根本不会飞(但有些鸟用腿游泳,而且很有效率)。

在对该属性调用函数之前,首先检查对象是否具有某个属性(在本例中为“legs”)是一种好的做法吗?

真的要看上下文了。作为一般规则,不,这被认为是不好的做法(参见“告诉不要问”和“德米特定律”),但有些情况下,这是合法的。而且,“好”的设计也取决于要解决的问题,我们在这里达到了玩具示例的极限,这些示例永远不能代表现实生活中的用例。

理论上,组合/委派对客户端代码应该是透明的,所以您应该只调用 whatever_animal.walk() 把它干掉。现在您(作为“客户代码”)可能想知道动物不能行走,在这种情况下,非行走动物应该提出一个例外时,收费步行…这也意味着 动物 必须有所有可能的“操作”的默认实现,并且客户机代码必须为“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())

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

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

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

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