当属性的方法需要修改所属类的状态时,如何将类与其属性解耦?

问题描述

属性方法需要修改所属类的状态时,如何将类与其属性解耦?或者,我如何重新设计架构,使这不是问题?

这个问题有点抽象,但我一次又一次地遇到这个问题。我花了很多时间设计我的代码库,以便我的类“高内聚低耦合”,但随着代码的发展,它们最终变得更加紧密耦合。

我将给出我一直在研究的最新示例。我有一个可以移动的 3 个轴的起重机类。最初的设计只有 1 个类,Crane,并且在 move_xmove_ymove_z 方法中重复了移动每个轴的业务逻辑}}。这是代码重复,所以这个功能被封装在一个Axis类中,然后起重机类由其中的3个组成,现在简单地委托Cranemove_xmove_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()

 

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...