如何实现类组合?继承与组合选型及属性访问优化问询
我有一个包含多个项的模型,所有项都具备一组共享属性:id、name、parent。此外,部分项仅用于容纳其他项,部分项用于连接其他项等。
在实现时,我考虑过使用继承,也考虑过使用组合(且看到很多建议优先选择组合)。于是我定义了一个仅用于提供共享属性访问与修改的基础类:
from attrs import define, Factory @define class BaseProperties: identifier: str name: str parent_id: Optional[str] @define class ConnectorItem: base_properties: BaseProperties connections: "List[ConnectorItems]" = Factory(list) @define class ContainerItem: base_properties: BaseProperties children: "List[Union[ConnectorItem, ContainerItem]]" = Factory(list)
但问题在于,当我想访问ContainerItem的name或id时,需要写成:
container_item.base_properties.identifier
或者在ContainerItem中定义property:
@property def identifier(self): return self.base_properties.identifier
才能直接使用:
container_item.identifier
第一种方式要求使用者了解ContainerItem的内部实现,且项目扩张后会出现大量多层点语法;第二种方式则需要在每个包含BaseProperties的类中重复编写大量getter和setter,十分繁琐。
我是否应该改用继承?虽然ContainerItem和ConnectorItem共享基础属性,但它们永远不会在同一上下文使用,我仅需要代码复用,不需要多态。
考虑到这是简化版,实际中ConnectorItem可能由不同类组合生成不同的ConnectorItemTypes,请问更好的实现方式是什么?
1. 直接用继承(最适合你的场景)
如果你只是需要代码复用而不需要多态,完全不用被「优先组合」的教条限制——这条建议主要针对需要多态、避免继承层级臃肿的场景,你的情况里继承就是最直接高效的方案:
from attrs import define, Factory from typing import Optional, List, Union @define class BaseItem: identifier: str name: str parent_id: Optional[str] @define class ConnectorItem(BaseItem): connections: List["ConnectorItem"] = Factory(list) @define class ContainerItem(BaseItem): children: List[Union[ConnectorItem, ContainerItem]] = Factory(list)
这样你可以直接访问container_item.identifier、connector_item.name,不需要额外的代理代码,简洁明了。
2. 坚持组合?用__getattr__自动代理属性
如果因为某些原因必须用组合(比如未来要动态替换基础属性逻辑),可以通过实现__getattr__方法自动代理base_properties的属性,避免重复编写大量@property:
from attrs import define, Factory from typing import Optional, List, Union @define class BaseProperties: identifier: str name: str parent_id: Optional[str] @define class ConnectorItem: base_properties: BaseProperties connections: List["ConnectorItem"] = Factory(list) def __getattr__(self, name): # 当访问的属性不在当前类时,自动从base_properties获取 return getattr(self.base_properties, name) @define class ContainerItem: base_properties: BaseProperties children: List[Union[ConnectorItem, ContainerItem]] = Factory(list) def __getattr__(self, name): return getattr(self.base_properties, name)
现在你同样可以直接写container_item.name,Python会自动帮你从base_properties中取对应属性。如果类自身有同名属性,会优先使用自身的,不会产生冲突。
3. 针对多类型ConnectorItem:继承基础属性 + 组合功能组件
如果需要扩展不同类型的ConnectorItem,推荐用「继承共享属性 + 组合不同功能组件」的方式,兼顾代码复用和灵活性:
from attrs import define, Factory from typing import Optional, List, Protocol # 定义连接逻辑的协议,规范不同Connector的行为 class ConnectionLogic(Protocol): def add_connection(self, item: "BaseItem") -> None: ... # 不同的连接逻辑实现 @define class DirectConnectionLogic: connections: List["BaseItem"] = Factory(list) def add_connection(self, item: "BaseItem") -> None: self.connections.append(item) @define class RestrictedConnectionLogic: connections: List["BaseItem"] = Factory(list) max_connections: int = 2 def add_connection(self, item: "BaseItem") -> None: if len(self.connections) < self.max_connections: self.connections.append(item) else: raise ValueError("超过最大连接数") # 共享基础属性的父类 @define class BaseItem: identifier: str name: str parent_id: Optional[str] # 组合不同逻辑的ConnectorItem @define class ConnectorItem(BaseItem): connection_logic: ConnectionLogic # 使用示例 direct_connector = ConnectorItem( identifier="conn1", name="直接连接", parent_id=None, connection_logic=DirectConnectionLogic() ) restricted_connector = ConnectorItem( identifier="conn2", name="限制连接", parent_id=None, connection_logic=RestrictedConnectionLogic(max_connections=3) )
这种方式既复用了基础属性,又能灵活扩展新的Connector类型——未来新增连接逻辑时,只需要实现ConnectionLogic协议即可,无需修改现有代码,符合开闭原则。
内容的提问来源于stack exchange,提问作者Al_

