向从抽象基础 class 派生的一组 classes 添加功能的正确 OOP 方法是什么?

What is the correct OOP way to add functionality to a set of classes deriving from an abstract base class?

设置

我的朋友告诉我,在 OOP 中,您通常不想修改现有代码库中的任何抽象基础 classes,因为这意味着您必须对每个派生的代码实施新的更改class。我感兴趣的是,人们更喜欢以一种最好的 pythonic 方式对代码库进行哪些修改。重点是更改现有代码库

示例场景

我有一个名为 Animal 的抽象基础 class,因为使用该库的代码必须与 Animal 对象交互。 我有多个子 class 实现:DogCat 每个都有一组不同的字段,每个字段都需要它们来实现自己的内部功能。所以此时代码库如下所示:

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def feed(self, foo: str) -> None:
        raise NotImplementedError


class Dog(Animal):
    def __init__(self):
        self.woof = "woof"

    def feed(self, food: str):
        print(f"Dog is being fed {food}. It says {self.woof}")


class Cat(Animal):
    def __init__(self):
        self.meow = "meow"
        self.purr = "purr"

    def feed(self, food: str):
        print(f"Cat is being fed {food}. It says {self.meow}")

修改

在这个实现之后,开发人员意识到他们想要记录来自 Animal 对象的相关字段(或状态),并且记录的数据从子 class 到子 class.

选项A

最初,我的想法是实现另一个 abstractmethod 并以此方式添加功能。这迫使每个 Animal 以他们需要的任何方式实施新的 get_fields()

class Animal(ABC):
    @abstractmethod
    def feed(self, foo: str) -> None:
        raise NotImplementedError

    @abstractmethod
    def get_fields(self) -> list:
        raise NotImplementedError

class Dog(Animal):
    def __init__(self):
        self.woof = "woof"

    def feed(self, food: str):
        print(f"Dog is being fed {food}. It says {self.woof}")

    def get_fields(self) -> list:
        return [self.woof]


class Cat(Animal):
    def __init__(self):
        self.meow = "meow"
        self.purr = "purr"

    def feed(self, food: str):
        print(f"Cat is being fed {food}. It says {self.meow}")

    def get_fields(self) -> list:
        return [self.meow, self.purr]

选项 B

我的朋友说我们不应该修改摘要 class 但是,我们想到的唯一其他选择是执行以下操作:

def get_field(animal: Animal) -> list:
    if isinstance(animal, Dog):
        return [animal.woof]
    elif isinstance(animal, Cat):
        return [animal.meow, animal.purr]
    else:
        raise TypeError

你会选择哪一个?还有另一种更好的方法吗?哪个更符合pythonic?

在 ABC 上实施通用机制,作为一种具体方法,但将 配置 下放到子class 中并且不要使用硬编码名称。

我在这里使用了 Meta 因为那是你在 Django 模型中看到的东西类型,name-spacing 什么是给定的 class 的 配置 嵌入 Meta class。 Django 特别使用 very similar system 来跟踪哪些字段显示在 auto-generated 管理面板中用于数据输入的位置。

from abc import ABC, abstractmethod

from typing import Optional, List

class Animal(ABC):

    class Meta:
        #could also set to [] as default...
        fields_of_interest : Optional[List[str]] = None

    @abstractmethod
    def feed(self, foo: str) -> None:
        raise NotImplementedError

    def get_fields(self) -> List:

        if self.Meta.fields_of_interest is None:
            # could also raise NotImplementedError("need to set `fields_of_interest` on class Meta in class {self.__class__.__name__}")
            return []

        res = [getattr(self, field) for field in self.Meta.fields_of_interest]
        return res

class Dog(Animal):

    class Meta:
        fields_of_interest = ["woof"]

    def __init__(self):
        self.woof = "woof"

    def feed(self, food: str):
        print(f"Dog is being fed {food}. It says {self.woof}")


class Cat(Animal):

    class Meta:
        fields_of_interest = ["purr", "meow"]

    def __init__(self):
        self.meow = "meow"
        self.purr = "purr"

    def feed(self, food: str):
        print(f"Cat is being fed {food}. It says {self.meow}")

class Mouse(Animal):

    def feed(self, foo: str) -> None:
        print(f"{self} feed")


for cls in [Cat, Dog, Mouse]:
    animal = cls()
    print(f"{animal} {animal.get_fields()}")

输出:

<__main__.Cat object at 0x1079f67d0> ['purr', 'meow']
<__main__.Dog object at 0x1079f6320> ['woof']
<__main__.Mouse object at 0x1079f67d0> []

此外,就抽象与具体而言,有助于创造性地思考以保持方法行为统一(因此通用),而不是过于挑剔。例如,无论是原始的设计模式书还是正在阅读它的人都在谈论复合模式,它处理“树”。好吧,他们说的是,当你在 Leaf 上(没有 children)并尝试迭代其 non-existent、children 时,他们可以 [=34] 而不是抛出异常=] 一个空列表。