向从抽象基础 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 实现:Dog
和 Cat
每个都有一组不同的字段,每个字段都需要它们来实现自己的内部功能。所以此时代码库如下所示:
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] 而不是抛出异常=] 一个空列表。
设置
我的朋友告诉我,在 OOP 中,您通常不想修改现有代码库中的任何抽象基础 classes,因为这意味着您必须对每个派生的代码实施新的更改class。我感兴趣的是,人们更喜欢以一种最好的 pythonic 方式对代码库进行哪些修改。重点是更改现有代码库。
示例场景
我有一个名为 Animal
的抽象基础 class,因为使用该库的代码必须与 Animal
对象交互。
我有多个子 class 实现:Dog
和 Cat
每个都有一组不同的字段,每个字段都需要它们来实现自己的内部功能。所以此时代码库如下所示:
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] 而不是抛出异常=] 一个空列表。