【问题标题】:Python 3: clean example for inheritance & abstract methods?Python 3:继承和抽象方法的干净示例?
【发布时间】:2018-02-16 12:22:36
【问题描述】:

实际的代码有很大的不同并且完全不同的主题,但我觉得这个小例子可能会更好,因为我的问题是理解复杂继承场景的关键概念(而不是我的特定领域)。

假设我们有一个基本的Entity 类:

from enum import Enum
from abc import abstractmethod

class Condition(Enum):
    ALIVE = 1
    DEAD = 2
    UNDEAD = 3

class Entity(object):

    def __init__(self):
        self.condition = Condition.ALIVE
        self.position = 0
        self.hitpoints = 100

    def move(self):
        self.position += 1

    def changeHitpoints(self, amount):
        self.hitpoints += amount

    @abstractmethod
    def attack(self, otherEntity):
        pass

这是其他具体实体继承自的基类,attack() 必须是抽象的,因为每个实体都应该实现自己的攻击方法风格。

现在我们可以实现一些实体:

class Orc(Entity):

    def __init__(self):
        super().__init__()
        self.hitpoints = 150
        self.damage = 10

    def attack(self, otherEntity : Entity):
        otherEntity.changeHitpoints(-self.damage)

class Human(Entity):

    def __init__(self):
        super().__init__()
        self.damage = 8

    def attack(self, otherEntity : Entity):
        otherEntity.changeHitpoints(-self.damage)

class Undead(Entity):

    def __init__(self):
        super().__init__()
        self.condition = Condition.UNDEAD
        self.damage = 5

    def attack(self, otherEntity : Entity):
        # harm enemy
        otherEntity.changeHitpoints(-self.damage)
        # heal yourself
        self.changeHitpoints(1)

这很好用。但是,我正在努力寻找一个好的解决方案 (DRY-style) 来实现“能力”和其他东西。

例如,如果OrcHuman 不仅要移动,还要能够跳跃,那么有这样的东西会很有趣:

class CanJump(Entity):

    def jump(self):
        self.position += 2

class Orc(Entity, CanJump):
    (...)

class Human(Entity, CanJump):
    (...)

这引入了两个问题。 (1) 我们需要在CanJump 中访问self.position,因此我们必须从Entity 继承?!如果这样做,我们必须在类CanJump 中实现抽象方法attack()。这没有任何意义,因为CanJump 应该只是赋予实体一种新型移动的能力。 (2) 将来我们可能想要实现例如一个装饰器,它在执行move()attack()、...之前检查实体的条件是否为Condition.DEAD,这也意味着CanJump需要访问@ 987654339@.

对于此类问题,什么是干净的解决方案?

如果需要进一步的子类化怎么办?例如。我们可能有兴趣创建一个UndeadHuman,例如class UndeadHuman(Undead, Human)。由于线性化(Undead 按顺序排列),它应该具有Undeadattack 行为,但它还需要来自HumanCanJump

【问题讨论】:

    标签: python python-3.x inheritance abstract-class multiple-inheritance


    【解决方案1】:

    [W]需要访问CanJump中的self.position,因此我们必须从Entity继承?!

    不,你没有。您可以将CanJump 视为一个混合类,它只是添加了功能。任何子类CanJump 的类都应具有position 属性。而CanJump 类本身不需要从Entity 继承。这样做:

    class CanJump:
        def jump(self):
            self.position += 2
    

    会很好的。然后你可以这样做:

    class Orc(Entity, CanJump):
        (...)
    
    class Human(Entity, CanJump):
        (...)
    

    这是一个完整的例子,证明上述方法可以工作:

    from abc import abstractmethod
    
    
    class A:
        def __init__(self):
            self.a = 0 
    
        @abstractmethod
        def m(self):
            pass
    
    
    class C:
        def c(self):
            self.a += 1
    
    
    class B(A, C):
        def __init__(self):
            super().__init__()
    
        def m(self):
            print('method in B')
    
    
    b = B()
    print(b.a) # 0
    b.c()
    print(b.a) # 1
    b.m() # method in B
    

    你看,你可以在方法实现中使用当前不存在的属性。属性只需要在调用方法时存在。并且让CanJump 的子类实现所需的属性效果很好。

    如果您想强制您的类的用户定义某些属性,您可以使用元类。我不会重复信息,而是将您指向@kindall's answer,它相当优雅地处理了这个问题。

    【讨论】:

    • 这是一个有趣的概念。这是与 Python / 鸭子类型相关的功能吗?我以前从未见过 mix-in 类。有没有办法防止CanJump 被直接实例化?有人可能会意外地这样做,然后self.position += 2 会有一个未解决的引用。另外,我想知道我们是否可以以某种方式键入提示属性?
    • @daniel451 一点也不,许多其他语言支持混合类。然而,它们可能被不同的名称调用,例如 PHP 中的特征。尽管在静态类型语言中实现起来有点棘手。稍后我将更新一个示例来解决您的其他问题。
    • @daniel451 这是多继承语言的常用技术。您现在可以在 Java 中使用提供默认方法的接口来做同样的事情。
    【解决方案2】:

    我很想通过组合而不是继承来对此进行建模。

    您可以有一个用于每个生物的Entity 类和一个单独的RaceModifier 类来设置相关的常量/调整和能力。然后每个生物都可以有一个列表来模拟游戏中的种族,例如:

    class RaceModifier:
        def __init__(self):
            ...
            self.abilities = []
    
    class Orc(RaceModifier):
        def __init__(self):
            super().__init__()
            self.extra_hit_points = 50
            self.extra_damage = 2
            self.abilities = [JUMPING]
    
    class Undead(RaceModifier):
        def __init__(self):
            super().__init__()
            self.abilities = [REGEN_ON_ATTACK]
    
    class Entity:
        ...
        def has_ability(self, ability): # ability = JUMPING etc
            for race in self.race_modifiers:
                if ability in race.abilities:
                    return True
            for eq in self.equipment: # wearing a ring of jumping ? :)
                if ability in eq.abilities:
                    return True
            return False
    

    【讨论】:

    • 这也是一个有趣的概念。你有更多(好的)Python composition 示例吗?
    • abilities 应该是dict,这样您就可以更快地检查出合适的能力。
    猜你喜欢
    • 2011-03-12
    • 2014-07-09
    • 1970-01-01
    • 2012-08-06
    • 2019-04-28
    • 2012-11-17
    • 2011-03-10
    • 2010-09-29
    • 1970-01-01
    相关资源
    最近更新 更多