我目前正在开发一种用于物理实验的新驱动程序系统。该系统将包含多个用于电子仪器的驱动程序,它们可能彼此非常不同。但是所有驱动程序都是由多个通道组成的,它们用于不同的事物。
为了使系统保持一致,我决定为驱动程序和通道定义基类。让我们将它们称为DriverBase
和ChannelBase
。所有驱动程序实现均基于这些基类。
例如:要开发用于仪器XY的驱动程序,需要实现类XYDriver
和XYChannel
,而对于仪器AB,则需要类ABDriver
和ABChannel
。
为了使系统万无一失,我想使用类型提示,因此没有人会尝试使用不兼容的类型。因此,基类如下所示:
class DriverBase:
def __init__(self):
# This list needs to be type-hinted, so the derived class only accepts a
# specific channel-type, that is related to this specific driver
# implementation. If the subclass tries to append another channel-type
# than expected, the user should be informed about this.
self._channels: List[ChannelBase] = []
# The users should know, what is returned by this property as exactly as
# possible.
@property
def channels(self) -> ChannelBase:
return self._channels
class ChannelBase:
def __init__(self, parent: DriverBase):
self._parent = parent
# The users should know, what is returned by this property as exactly as
# possible.
@property
def parent_device(self) -> DriverBase:
return self._parent
# The subclasses should be as simple as possible, because those are
# implemented by the users themselves.
class XYChannel(ChannelBase):
# ...
class ABChannel(ChannelBase):
# ...
class XYDriver(DriverBase):
def __init__(self, channel_count: int):
super().__init__()
# This is how it should be used
for i in range(channel_count):
self._channels.append(XYChannel(i))
class ABDriver(DriverBase):
def __init__(self, channel_count: int):
super().__init__()
# Here a user made a typical copy-paste-mistake. Maybe the channels
# of both devices are very similar so that the script will terminate
# without any error, but the instrument channels did not get any
# data. So it would be very nice, if the IDE automatically warns the
# user, that this type is not allowed.
for i in range(channel_count):
self._channels.append(XYChannel(i)) # ABChannel would be correct
现在,我可以确定
DriverBase
有一个ChannelBase
列表,并且每个ChannelBase
都有一个DriverBase
作为父对象。这可以正常工作,如果实现有误,我的IDE会提醒我。但是因为我想让系统变得万无一失,而且我不是完美主义者,所以我也希望在子类中保持这种行为,而不用重新定义
channels
和parent_device
属性,因为实际上还有两个属性。我需要防止用户混淆子类,例如在XYChannel
中使用ABDriver
或在ABChannel
中使用XYDriver
。这就是我要避免的。最简单的方法是使这些属性抽象化,并迫使用户在被覆盖的属性中进行新的类型提示。但这不是我想要的...我正在寻找一个完全位于基类中的解决方案。我希望有某种设计模式可以将相关的类保持在一起,并防止它们与这种关系之外的类进行交互。你们对此有什么想法吗?我希望有人能理解我的意思,因为它似乎非常具体,真的很难为我解释。
提前致谢。
更新资料
这个问题背后的想法如下。我的用户(物理学家和技术人员)使用我的驱动程序框架来自动化他们的测量,他们还将开发自己的驱动程序。我将使用自定义异常和所有用户提供的参数的类型/值检查来使框架尽可能地简单。但是事实并非如此……我想要的不是必须的。但是,如果用户在编写代码时收到警告,那就太好了。因此,他们不必等到可以测试代码即可知道它们是否有效。
最佳答案
您的用户打算编写代码吗?您已经在基类中定义了接口,不确定要实现的目标是什么,但是可以在其中添加isinstance()类型的检查
for i in range(channel_count):
if not isinstance(ChannelBase, XYChannel(i)):
raise IncompatibleChannelException('your message here')
self._channels.append(XYChannel(i))
(与另一个相同,但交换了类)。或者如您所说,使它们抽象,以便他们知道自己正在实现正确的类。
如果要遵循此建议,则可以添加一个特定的方法以在初始化时进行类型检查(在初始化执行期间调用)。
就像是;
def __init__(self, channel_class):
# This list needs to be type-hinted, so the derived class only accepts a
# specific channel-type, that is related to this specific driver
# implementation. If the subclass tries to append another channel-type
# than expected, the user should be informed about this.
self._channels: List[ChannelBase] = []
self.channel_class = channel_class
self.type_check_on_init()
for i in range(channel_count):
self._channels.append(self.channel_class(i))
def type_check_on_init(self):
if not isinstance(ChannelBase, self.channel_class):
raise IncompatibleChannelException('Channel must be of type {} not {}'.format(ChannelBase, self.channel_class)
如果ABDriver始终与ABChannel一起使用,则可以使它们成为类的抽象属性,并迫使人们实现它们:
class DriverBase:
def __init__(self):
# This list needs to be type-hinted, so the derived class only accepts a
# specific channel-type, that is related to this specific driver
# implementation. If the subclass tries to append another channel-type
# than expected, the user should be informed about this.
self._channels: List[ChannelBase] = []
# The users should know, what is returned by this property as exactly as
# possible.
@abc.abstractproperty
def channel_class(self):
pass
这样,每个子类都必须指定一个可以实现的通道类
关于python - 相关基类的设计模式,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59307450/