如何解决当属性的方法需要修改所属类的状态时,如何将类与其属性解耦?
当属性的方法需要修改所属类的状态时,如何将类与其属性解耦?或者,我如何重新设计架构,使这不是问题?
这个问题有点抽象,但我一次又一次地遇到这个问题。我花了很多时间设计我的代码库,以便我的类“高内聚低耦合”,但随着代码的发展,它们最终变得更加紧密耦合。
我将给出我一直在研究的最新示例。我有一个可以移动的 3 个轴的起重机类。最初的设计只有 1 个类,Crane
,并且在 move_x
、move_y
和 move_z
方法中重复了移动每个轴的业务逻辑}}。这是代码重复,所以这个功能被封装在一个Axis类中,然后起重机类由其中的3个组成,现在简单地委托Crane
、move_x
和move_y
方法到适当的轴。即:
move_z
在本例中,Axis 与 Crane 分离。我可以单独测试 Axis(即我不需要 Crane 对象来测试 Axis),并且对 Crane 的更改根本不会影响 Axis。依赖是一个方向,Crane依赖于Axis,但Axis不依赖于Crane。
然后我们决定 Crane 需要一个状态属性来表示当前是否有任何轴在运动,并且它还需要一种取消任何运动的方法。
状态属性和表示请求中止的状态都属于起重机,但它们需要通过属于轴的方法进行修改。我最初将这些作为参数传递给 Axis import asyncio
class CraneAxis:
def __init__(self):
self.__position: float = 0.0
@property
def position(self):
return self.__position
@position.setter
def position(self,value: float):
self.__position = value
print(f'new position: {value}')
async def move(self,target_position: float):
dt=0.1
crawl_veLocity=0.5
if target_position > self.position:
while self.position < target_position:
self.position += crawl_veLocity * dt
await asyncio.sleep(dt)
else:
while self.position > target_position:
self.position -= crawl_veLocity * dt
await asyncio.sleep(dt)
class Crane:
def __init__(self):
self.x_axis = CraneAxis()
self.y_axis = CraneAxis()
self.z_axis = CraneAxis()
async def move_x(self,target_position: float):
await self.x_axis.move(target_position)
async def move_y(self,target_position: float):
await self.y_axis.move(target_position)
async def move_z(self,target_position: float):
await self.z_axis.move(target_position)
方法:即:
move()
async def move(self,target_position: float,status: Cranestatus,mode: CraneMode):
和 Cranestatus
是枚举。这与 Crane 稍微耦合,但仍然非常解耦,因为只要您传递一个 CraneMode
和一个 Cranestatus
,它就可以被任何东西使用。即它不关心任何实现细节,它只需要这些简单的东西并直接使用它们。
但是当我使用 python 时,这些属性在 CraneMode
方法中是不可变的,如果我试图从函数中更改值,我只会创建一个新实例。因此,我将拥有的起重机传递给 Axis move()
方法。即:
move()
此时,我注意到我的代码库中不断发生一些事情。通常,当我使用组合时,我最终会将 import asyncio
from enum import IntEnum
class Cranestatus(IntEnum):
BUSY=0,READY=1
class CraneMode(IntEnum):
READY=0,MOVE=1,ABORT=2
class CraneAxis:
def __init__(self):
self.__position: float = 0.0
@property
def position(self):
return self.__position
@position.setter
def position(self,owner: 'Crane'):
if owner.crane_status == Cranestatus.BUSY:
return
owner.crane_status = Cranestatus.BUSY
owner.crane_mode = CraneMode.MOVE
dt=0.1
crawl_veLocity=0.5
if target_position > self.position:
while (self.position < target_position
and owner.crane_mode != CraneMode.ABORT):
self.position += crawl_veLocity * dt
await asyncio.sleep(dt)
else:
while (self.position > target_position
and owner.crane_mode != CraneMode.ABORT):
self.position -= crawl_veLocity * dt
await asyncio.sleep(dt)
owner.crane_status = Cranestatus.READY
owner.crane_mode = CraneMode.READY
class Crane:
def __init__(self):
self.__crane_status = Cranestatus.READY
self.__crane_mode = CraneMode.READY
self.x_axis = CraneAxis()
self.y_axis = CraneAxis()
self.z_axis = CraneAxis()
@property
def crane_status(self):
return self.__crane_status
@crane_status.setter
def crane_status(self,value: Cranestatus):
self.__crane_status = value
print(f'new crane status: {value}')
@property
def crane_mode(self):
return self.__crane_mode
@crane_mode.setter
def crane_mode(self,value: CraneMode):
self.__crane_mode = value
print(f'new crane mode: {value}')
async def move_x(self,target_position: float):
await self.x_axis.move(target_position,self)
async def move_y(self,target_position: float):
await self.y_axis.move(target_position,self)
async def move_z(self,target_position: float):
await self.z_axis.move(target_position,self)
def abort(self):
self.crane_mode = CraneMode.ABORT
参数传递到用于组合所属类的对象的方法(或构造函数)中。即在此示例中,现在需要向 Axis 传递 Crane 对象。我已经失去了解耦。我现在需要一个 Crane 对象来测试 Axis,而 Axis 对 Crane 的变化很敏感。依赖有两个方向,Crane依赖Axis,Axis依赖Crane。
也许这根本不是问题,将这些类耦合起来也没什么大不了的。但是我被告知紧密耦合是不好的。是这样吗?
如果我确实想将 Axis 和 Crane 分离,那么最好的方法是什么?
谢谢!
编辑:简单说一下,这是一个关于代码质量和可维护性的问题,而不是关于让某些东西工作的问题。上面示例中的代码的行为完全符合我的要求,我只是想让实现更好。
另外,我知道 python 是动态类型的,并且我在上面的示例中以静态类型的方式使用它,但是我在静态类型语言中也有这个问题,并且想要一个我可以在任何语言中使用的解决方案。此外,对于这个项目,我们决定使用 owner
对代码库进行类型检查,并尝试以更强类型的方式使用事物来避免错误。
解决方法
您可以通过创建接口来从具体类中抽象依赖项来实现这一点。因此,不是将 Crane 传递到 Axis,而是传递 Crane 子类的 Moveable。现在,您可以创建不需要 Cranes 的单独测试存根,并且不关心您使用的是 Moveable Cranes 的事实。
为了使其在语言上更加分离,我将 CraneStatus 更改为 MoveableStatus,将 CraneMode 更改为 MoveableMode。
,我找到了一种将 Axis 与 Crane 完全分离的解决方案。我需要将 Axis 使用的所有依赖项从 Crane 移动到 Axis 本身。
在原始问题的第二个代码片段中,轴查看 Crane 以查看是否已请求中止,并告诉 Crane 现在有东西正在移动,因此它应该变为 BUSY。
相反,我们应该在 Axis 类本身内封装中止移动和发出信号是否为 BUSY 的能力。为什么Axis需要一台起重机来做这些事情,它应该可以自己做。
Crane 然后可以将其 abort 方法委托给 Axis,并且还可以观察轴以查看它何时变为 BUSY,并在它收到通知时使其变为 BUSY。这可以通过观察者模式来完成。
作为一个额外的好处,这已从 Axis move()
方法中删除了“按引用调用”。即,我们过去常常传入一个将在函数内更改的参数,并且该更改在父作用域中持续存在。这被认为是一种反模式。新设计不使用“按引用调用”:
import asyncio
from enum import IntEnum
from typing import Callable,List
class AxisStatus(IntEnum):
BUSY=0
READY=1
class CraneStatus(AxisStatus): pass
class AxisMode(IntEnum):
READY=0
MOVE=1
JOG=2
ABORT=3
class CraneMode(AxisMode): pass
class Axis:
def __init__(self):
self.__position: float = 0.0
self.__axis_status = AxisStatus.READY
self.axis_status_observers: List[Callable[[AxisStatus],None]] = []
self.__axis_mode = AxisMode.READY
self.axis_mode_observers: List[Callable[[AxisMode],None]] = []
@property
def position(self):
return self.__position
@position.setter
def position(self,value: float):
self.__position = value
print(f'new position: {value}')
@property
def axis_status(self):
return self.__axis_status
@axis_status.setter
def axis_status(self,value: AxisStatus):
self.__axis_status = value
print(f'new axis status: {value}')
#notify observers
for cb in self.axis_status_observers:
cb(value)
@property
def axis_mode(self):
return self.__axis_mode
@axis_mode.setter
def axis_mode(self,value: AxisMode):
self.__axis_mode = value
print(f'new axis mode: {value}')
#notify observers
for cb in self.axis_mode_observers:
cb(value)
async def move(self,target_position: float):
if self.axis_status == AxisStatus.BUSY:
return
self.axis_status = AxisStatus.BUSY
self.axis_mode = AxisMode.MOVE
dt=0.1
crawl_velocity=0.5
if target_position > self.position:
while (self.position < target_position
and self.axis_mode != AxisMode.ABORT):
self.position += crawl_velocity * dt
await asyncio.sleep(dt)
else:
while (self.position > target_position
and self.axis_mode != AxisMode.ABORT):
self.position -= crawl_velocity * dt
await asyncio.sleep(dt)
self.axis_status = AxisStatus.READY
self.axis_mode = AxisMode.READY
def abort(self):
self.axis_mode = AxisMode.ABORT
class Crane:
def __init__(self):
self.__crane_status = CraneStatus.READY
self.__crane_mode = CraneMode.READY
self.x_axis = Axis()
self.y_axis = Axis()
self.z_axis = Axis()
#whenever an axis status or mode changes,reflect this change in the crane
def axis_status_observer(axis_status: AxisStatus):
#if any axis is busy,crane is busy
if axis_status == AxisStatus.BUSY:
self.__crane_status = CraneStatus.BUSY
#if all axis are ready,crane is ready
if all([axis.axis_status == AxisStatus.READY for axis in [self.x_axis,self.y_axis,self.z_axis]]):
self.__crane_status = CraneStatus.READY
self.x_axis.axis_status_observers.append(axis_status_observer)
def axis_mode_observer(axis_mode: AxisMode):
#only one axis can be in any other mode than READY at a time
#the crane mode is whatever this axis mode is
self.__crane_mode = CraneMode(axis_mode)
self.x_axis.axis_mode_observers.append(axis_mode_observer)
@property
def crane_status(self):
return self.__crane_status
@crane_status.setter
def crane_status(self,value: CraneStatus):
self.__crane_status = value
print(f'new crane status: {value}')
@property
def crane_mode(self):
return self.__crane_mode
@crane_mode.setter
def crane_mode(self,value: CraneMode):
self.__crane_mode = value
print(f'new crane mode: {value}')
async def move_x(self,target_position: float):
await self.x_axis.move(target_position)
async def move_y(self,target_position: float):
await self.y_axis.move(target_position)
async def move_z(self,target_position: float):
await self.z_axis.move(target_position)
def abort(self):
self.x_axis.abort()
self.y_axis.abort()
self.z_axis.abort()
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。