在从抽象基类派生的一组类中添加功能的正确方法是什么?

发布于 2025-01-24 18:00:11 字数 2262 浏览 1 评论 0原文

设置

我的朋友告诉我,在OOP中,您通常不想修改现有代码库中的任何抽象基类,因为这意味着您必须对每个派生的类实施新的更改。我对使用Pythonic的方式更喜欢对代码库进行的修改感兴趣。重点是更改现有代码库。

示例场景

我有一个抽象基类,称为Animal,因为使用此库的代码必须与Animal对象进行交互。 我有多个儿童类实现: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对象登录相关字段(或状态),以及记录的数据是什么因素而变化。儿童课程的儿童课。

选项A

最初,我的想法是实现另一个AbstractMethod并以这种方式添加功能。这迫使每个动物>以任何方式实现新的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

我的朋友说我们不应该修改抽象类,但是,我们提出的唯一其他选择是要做以下操作:

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?

Setting

My friend told me that in OOP, you generally don't want to modify any abstract base classes in an existing codebase, because that means you have to implement the new changes to each and every derived class. I'm interested in which modification to the codebase one would prefer in a preferably pythonic way. The emphasis is on changing an existing codebase.

Example scenario

I have an abstract base class called Animal, because the code that uses this library has to interact with Animal objects.
I have multiple child class implementations: Dog and Cat that each have a different set of fields that they each need for their own inner functionality. So at this point the code base looks like:

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}")

Modification

AFTER this is implemented, the developers realize that they want to log the relevant fields (or states) from an Animal objects and what the logged data is varies from child class to child class.

Option A

Originally, my idea would be to implement another abstractmethod and add functionality that way. This forces every Animal to implement the new get_fields() in whatever way they need to.

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]

Option B

My friend is saying we shouldn't modify the abstract class however, the only other option we came up with is to do the following:

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

Which one would you go with? Is there another, better way to do this? Which one is more pythonic?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

君勿笑 2025-01-31 18:00:11

在ABC上实现一种通用机制,作为具体方法,但将配置转移到子类,并且不使用硬编码名称。

我在此处使用了meta,因为这是您在Django模型中看到的东西的类型,在嵌入式meta 中,name-stacs name-stacs name-stacs in name-stacs in name-spacs in name-space /代码>类。 django专门使用非常相似的系统哪些字段显示用于数据输入的自动生成管理面板中的位置。

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> []

此外,就抽象与混凝土而言,它有助于创造性地思考以保持方法行为统一(并因此是通用),而不是过于挑剔。例如,要么原始设计模式书或一本带有一本书是在谈论复合图案,该图案涉及“树”。好吧,他们所说的是,当您在叶子上(没有孩子)并试图迭代其不存在的孩子时,他们可以返回一个空名单,而不是抛出例外。

Implement a generic mechanism on the ABC, as a concrete method, but devolve the configuration to the subclasses and DONT use hardcoded names.

I've used Meta here because that is the type of stuff you see in Django models, name-spacing what is a given class's configuration in an embedded Meta class. Django specifically uses a very similar system to track which fields get display where in the auto-generated admin panels used for data entry.

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()}")

output:

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

Also, in terms of abstract vs concrete, it helps to think creatively to keep method behavior uniform (and therefore generic), rather than being overly picky. For example, either the original Design Patterns book or one taking it up was talking the Composite Pattern, which deals with "trees". Well, what they said was that instead of throwing an exception when you are on a Leaf (no children) and trying to iterate its, non-existent, children, they could just return an empty list.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文