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

实际上xD 有点过头了

继承描述的是一种"是"的关系,组合描述的是"有"的关系。因此,在您的情况下,对翅膀和腿等属性使用composition是完全合理的,但Bird、Cat和Dog是动物——它们没有"拥有"动物(好吧,它们都有跳蚤,但这是另一个话题)——所以它们应该继承Animal

此外,大多数鸟类的腿也不灵活,相当多的鸟类实际上根本不飞(但有些鸟类用腿游泳,而且效率很高);-)

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

实际上取决于上下文。一般来说,不,这不被认为是好的做法(参见"告诉不要问"one_answers"需求法则"),但在某些情况下它是合法的。此外,"好"的设计也取决于要解决的问题,我们在这里达到了玩具示例的极限,这些玩具示例永远不能代表现实生活中的用例。

从理论上讲,组合/委派应该对客户端代码透明,所以你只需要调用whatever_animal.walk()就可以了。现在你(作为"客户端代码")可能想知道动物不能走路,在这种情况下,非行走的动物在被要求走路时应该引发异常。。。这也意味着Animal必须为所有可能的"操作"都有一个默认实现,并且客户端代码必须为"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

但同样,如果没有真正的问题需要解决,这些都没有意义,而真正的问题通常定义了正确的解决方案。

最新更新