我目前正在开发一种用于物理实验的新驱动程序系统。该系统将包含多个用于电子仪器的驱动程序,它们可能彼此非常不同。但是所有驱动程序都是由多个通道组成的,它们用于不同的事物。

为了使系统保持一致,我决定为驱动程序和通道定义基类。让我们将它们称为DriverBaseChannelBase。所有驱动程序实现均基于这些基类。

例如:要开发用于仪器XY的驱动程序,需要实现类XYDriverXYChannel,而对于仪器AB,则需要类ABDriverABChannel
为了使系统万无一失,我想使用类型提示,因此没有人会尝试使用不兼容的类型。因此,基类如下所示:

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会提醒我。
但是因为我想让系统变得万无一失,而且我不是完美主义者,所以我也希望在子类中保持这种行为,而不用重新定义channelsparent_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/

10-14 10:31