我如何以编程方式决定将对象创建为哪个类?

问题描述 投票:0回答:1

我想编写代码来与不同定位风格(绝对与相对)的编码器进行通信。我的想法是创建一个具有所有编码器共有的东西的基类。然后会有两个子类,一个是绝对的,一个是相对的。然后每个品牌都会有相应的子课程。每个班级都会有独特的沟通功能。

层次结构示意图:

假设我有一种方法可以按品牌和类型识别每个电机,我将如何以编程方式决定要创建什么类对象?

python inheritance subclass abstraction hardware-interface
1个回答
0
投票

正如您已经暗示的那样,这听起来像是继承的绝佳情况,并且可以通过这种方式很快解决。我为您整理了一些示例代码,展示了如何实现这一目标。

您提出的一个观点我想争辩一下:

您提到每个类别都有独特的功能。我实际上建议完全相反,给每个类相同的核心功能,然后在它们初始化之后,你永远不需要担心你实际得到的是哪一个,因为它们都遵循相同的接口。这可以极大地简化您未来的代码,因为您无需检查正在使用哪个类,并且您的代码将可以在不同电机之间重用,无需任何重写,只需更改电机品牌和名称即可。

要回答这个问题我将如何以编程方式决定创建什么类对象?:我推荐一本以品牌名称和型号名称元组为键的字典,以适当构建的类定义作为值。如果两个单独的模型使用相同的接口,您可以简单地将它们映射到相同的类定义。我有一个名为

MOTOR_ENCODER_LOOKUP
.

的示例实现
import enum
import typing


class PositioningType(enum.Enum):
    relative = 'relative'
    absolute = 'absolute'


class EncoderBase:
    def __init__(
            self,
            pos_type: PositioningType,
            motor_brand: str,
            motor_model: str,
            address: str
    ):
        self.pos_type = pos_type
        self.motor_brand = motor_brand
        self.motor_model = motor_model
        self.address = address
        self._setup_hardware_interface()
        self._last_commanded_state = None
        self._last_commanded_state = self.get_position()

    def move_relative(self, relative_change: float) -> bool:
        raise NotImplementedError

    def move_absolute(self, new_position: float) -> bool:
        raise NotImplementedError

    def get_position(self) -> float:
        # Do something to get the position from the hardware
        # May want to override this in the motor-specific classes
        return self._last_commanded_state or 0.0

    def _setup_hardware_interface(self) -> bool:
        # Whatever specific setup is necessary for each encoder can be
        #   performed in the derived classes
        #   i.e., open a COM port, setup UDP, etc
        raise NotImplementedError

    def _send_command(self, commandvalue: float) -> bool:
        raise NotImplementedError

    def __repr__(self):
        outstr = ''
        outstr += f'<{type(self).__name__}\n'
        outstr += f'  positioning type: {self.pos_type}\n'
        outstr += f'  motor brand:      {self.motor_brand}\n'
        outstr += f'  motor model:      {self.motor_model}\n'
        outstr += f'  hardware address: {self.address}\n'
        outstr += f'  last position:    {self._last_commanded_state}\n'
        outstr += f'/>'
        return outstr

    @property
    def pos_type(self) -> PositioningType:
        return self._pos_type

    @pos_type.setter
    def pos_type(self, invalue: PositioningType):
        self._pos_type = invalue

    @property
    def motor_brand(self) -> str:
        return self._motor_brand

    @motor_brand.setter
    def motor_brand(self, invalue: str):
        self._motor_brand = invalue

    @property
    def motor_model(self) -> str:
        return self._motor_model

    @motor_model.setter
    def motor_model(self, invalue: str):
        self._motor_model = invalue

    @property
    def address(self) -> str:
        return self._address

    @address.setter
    def address(self, invalue: str):
        self._address = invalue


class AbsoluteEncoder(EncoderBase):
    def __init__(self, motor_brand: str, motor_model: str, address: str):
        super().__init__(PositioningType.absolute, motor_brand, motor_model, address)

    def move_relative(self, relative_change: float) -> bool:
        # This is inherently an absolute encoder, so calculate the new absolute
        #   position and send that
        new_position = self.get_position() + relative_change
        return self.move_absolute(new_position)

    def move_absolute(self, new_position: float) -> bool:
        # This is already an absolute encoder, so send the command as-is
        success = self._send_command(new_position)
        if success:
            self._last_commanded_state = new_position
        return success


class RelativeEncoder(EncoderBase):
    def __init__(self, motor_brand: str, motor_model: str, address: str):
        super().__init__(PositioningType.relative, motor_brand, motor_model, address)

    def move_relative(self, relative_change: float) -> bool:
        # This is already a relative encoder, so send the command as-is
        success = self._send_command(relative_change)
        if success:
            self._last_commanded_state += relative_change
        return success

    def move_absolute(self, new_position: float) -> bool:
        # This is inherently a relative encoder, so calculate the relative change
        #   and send the relative command
        relative_change = new_position - self.get_position()
        return self.move_relative(relative_change)


class EncoderAlphaOne(AbsoluteEncoder):
    def _send_command(self, commandvalue: float) -> bool:
        # do something to send the command
        return True

    def _setup_hardware_interface(self) -> bool:
        return True

    def get_position(self) -> float:
        # Ask the hardware for its current position since AbsoluteEncoders probably
        #   have that feature
        return self._last_commanded_state or 0.0


class EncoderAlphaTwo(RelativeEncoder):
    def _send_command(self, commandvalue: float) -> bool:
        # do something to send the command
        return True

    def _setup_hardware_interface(self) -> bool:
        return True


class EncoderBetaOne(AbsoluteEncoder):
    def _send_command(self, commandvalue: float) -> bool:
        # do something to send the command
        return True

    def _setup_hardware_interface(self) -> bool:
        return True

    def get_position(self) -> float:
        # Ask the hardware for its current position since AbsoluteEncoders probably
        #   have that feature
        return self._last_commanded_state or 0.0


# Add all your various make/model of encoders here with appropriate classes
#   and encoder_factory will automatically grab it
# Each encoder needs to have a class definition written, as shown above
#   but most of the work is done in one of the parent classes, so it should be quick
MOTOR_ENCODER_LOOKUP = {
    ('AlphaCompany', 'Model1'): EncoderAlphaOne,
    ('AlphaCompany', 'Model2'): EncoderAlphaTwo,
    ('BetaCompany', 'FirstModel'): EncoderBetaOne
}


# A factory function to go grab the correct class definition and initialize it
def encoder_factory(motor_brand: str, motor_model: str, address: str):
    return MOTOR_ENCODER_LOOKUP[(motor_brand, motor_model)](
        motor_brand,
        motor_model,
        address
    )


def _main():
    # Demonstrate that the functionality basically works
    # Use three separate types of encoder without fussing about which one you have
    e1 = encoder_factory('AlphaCompany', 'Model1', 'COM1')
    e2 = encoder_factory('AlphaCompany', 'Model2', 'COM3')
    e3 = encoder_factory('BetaCompany', 'FirstModel', 'COM4')
    e1.move_relative(25.0)
    e1.move_relative(-5.0)
    e2.move_relative(10.0)
    e2.move_absolute(45.0)
    e3.move_absolute(60.0)
    e3.move_relative(10.0)
    print(e1)
    print(e2)
    print(e3)


if __name__ == '__main__':
    _main()

运行上述产量:

<EncoderAlphaOne
  positioning type: PositioningType.absolute
  motor brand:      AlphaCompany
  motor model:      Model1
  hardware address: COM1
  last position:    20.0
/>
<EncoderAlphaTwo
  positioning type: PositioningType.relative
  motor brand:      AlphaCompany
  motor model:      Model2
  hardware address: COM3
  last position:    45.0
/>
<EncoderBetaOne
  positioning type: PositioningType.absolute
  motor brand:      BetaCompany
  motor model:      FirstModel
  hardware address: COM4
  last position:    70.0
/>

如果您有任何后续问题,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.