我想编写代码来与不同定位风格(绝对与相对)的编码器进行通信。我的想法是创建一个具有所有编码器共有的东西的基类。然后会有两个子类,一个是绝对的,一个是相对的。然后每个品牌都会有相应的子课程。每个班级都会有独特的沟通功能。
层次结构示意图:
假设我有一种方法可以按品牌和类型识别每个电机,我将如何以编程方式决定要创建什么类对象?
正如您已经暗示的那样,这听起来像是继承的绝佳情况,并且可以通过这种方式很快解决。我为您整理了一些示例代码,展示了如何实现这一目标。
您提出的一个观点我想争辩一下:
您提到每个类别都有独特的功能。我实际上建议完全相反,给每个类相同的核心功能,然后在它们初始化之后,你永远不需要担心你实际得到的是哪一个,因为它们都遵循相同的接口。这可以极大地简化您未来的代码,因为您无需检查正在使用哪个类,并且您的代码将可以在不同电机之间重用,无需任何重写,只需更改电机品牌和名称即可。
要回答这个问题我将如何以编程方式决定创建什么类对象?:我推荐一本以品牌名称和型号名称元组为键的字典,以适当构建的类定义作为值。如果两个单独的模型使用相同的接口,您可以简单地将它们映射到相同的类定义。我有一个名为
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
/>
如果您有任何后续问题,请告诉我。