python - 在 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()
解决方案
实际上你已经有点过头了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
但是再一次,如果没有真正的问题要解决,这一切都没有意义,这通常定义了正确的解决方案。
推荐阅读
- curl - Tesla API(非官方)- 通过 cURL 进行测试
- regex - 在 Perl 中匹配数组中的精确字符串
- javascript - 用于捕获第一个父级的子级属性值的正则表达式
- angular7 - 完全匹配的垫表过滤器
- java - 活动不适用于添加导航抽屉
- xamarin - 单击“单元格控件”之类的按钮时获取ListView项目
- web-scraping - 如何在使用 Javascript 或 Json 的 Reddit 中抓取具有相同类名的项目?
- vue.js - Vue.js:字段值一旦失去焦点就会返回
- python - 如何计算重复的数组值并返回它们的索引位置?
- python - 如何在 Unix 平台上为机器人框架 API python 测试创建 jenkins 作业