当属性的方法需要修改所属类的状态时,如何将类与其属性解耦? [英] How to decouple a class from it's attributes when the methods of the attributes need to modify state of the owning class?

查看:28
本文介绍了当属性的方法需要修改所属类的状态时,如何将类与其属性解耦?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How do I decouple a class from its attributes when methods of the attributes need to modify the state of the owning class? Alternatively, how can I redesign the architecture so that this isn't a problem?

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

This question is a bit abstract, but I keep coming across this problem time and time again. I spend a lot of time designing my code base so that my classes are "high cohesion and low coupling", but then as the code evolves over time, they end up becoming more closely coupled.

我将给出我一直在研究的最新示例.我有一个可以移动的 3 个轴的起重机类.最初的设计只有1个类,Crane,并且在move_xmove_y中重复了移动每个轴的业务逻辑Crane 的 >move_z 方法.这是代码重复,所以这个功能被封装在一个Axis类中,然后起重机类由其中的3个组成,现在简单地委托move_xmove_y,和 move_z 方法到适当的轴.即:

I'll give the latest example I have been working on. I have a Crane class with 3 Axis that can be moved. The original design had only 1 class, Crane, and the business logic for moving each axis was repeated in the move_x, move_y, and move_z methods of the Crane. This was code duplication, so this functionality was encapsulated inside an Axis class, and then the crane class was composed of 3 of these, and now simply delegated the move_x, move_y, and move_z methods to the appropriate Axis. i.e:

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)

在本例中,Axis 与 Crane 分离.我可以单独测试 Axis(即我不需要 Crane 对象来测试 Axis),并且对 Crane 的更改根本不会影响 Axis.依赖是一个方向,Crane依赖于Axis,但Axis不依赖于Crane.

In this example, the Axis is decoupled from the Crane. I can test the Axis in isolation (i.e I do not need a Crane object to test the Axis), and changes to the Crane do not affect the Axis at all. The dependency is in one direction, the Crane depends on the Axis, but the Axis does not depend on the Crane.

然后我们决定 Crane 需要一个状态属性来表示当前是否有任何轴在运动,并且它还需要一种取消任何运动的方法.

We then decide that the Crane needs a status attribute that represents if any axis is currently in motion, and also it needs a way to cancel any movement.

状态属性和表示请求中止的状态都属于起重机,但它们需要通过属于轴的方法进行修改.我最初将这些作为参数传递给 Axis move() 方法:即:

The status attribute and the state that signals that an abort has been requested both belong to the crane, but they will need to be modified by the method belonging to the axis. I originally passed these in as parameters to the Axis move() method: i.e:

async def move(self, target_position: float, status: CraneStatus, mode: CraneMode):  

CraneStatusCraneMode 是枚举.这与 Crane 稍微耦合,但仍然非常解耦,因为只要你传递一个 CraneStatus 和一个 CraneMode.即它不关心任何实现细节,它只需要这些简单的东西并直接使用它们.

CraneStatus and CraneMode are enums. This is slightly more coupled to the Crane, but still pretty decoupled as it could be used by anything as long as you pass it a CraneStatus and a CraneMode. i.e it does not care about any implementation details, it just needs these simple things and it uses them directly.

但是当我使用 python 时,这些属性在 move() 方法中是不可变的,如果我试图从函数中更改值,我只会创建一个新实例.因此,我将拥有的起重机传递给 Axis move() 方法.即:

But as I am using python these attributes would be immutable within the move() method, and if I tried to change the value from within the function, I would instead just create a new instance. So instead I passed the owning crane into the Axis move() method. i.e:

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, value: float):
        self.__position = value
        print(f'new position: {value}')
    
    async def move(self, target_position: float, 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

此时,我注意到我的代码库中不断发生一些事情.通常,当我使用组合时,我最终会将 owner 参数传递到用于组合所属类的对象的方法(或构造函数)中.即在这个例子中,Axis 现在需要传递 Crane 对象.我已经失去了解耦.我现在需要一个 Crane 对象来测试 Axis,而 Axis 对 Crane 的变化很敏感.依赖有两个方向,Crane依赖Axis,Axis依赖Crane.

At this point, I noticed something that keeps happening in my codebases. Often when I use composition, I end up passing an owner parameter into the methods (or the constructor) of the object that is being used to compose the owning class. i.e in this example, the Axis now needs to be passed the Crane object. I have lost the decoupling. I now need a Crane object to test the Axis, and the Axis is sensitive to changes in the Crane. The dependency is in two directions, the Crane depends on the Axis, and the Axis depends on the Crane.

也许这根本不是问题,将这些类耦合起来也没什么大不了的.但是我被告知紧密耦合是不好的.是这样吗?

Perhaps this isn't a problem at all, and having these classes coupled is no big deal. But I have been taught that tight coupling is bad. Is this the case?

如果我确实想将 Axis 和 Crane 分离,那么最好的方法是什么?

If I did want to decouple the Axis and the Crane, what would be the best way to go about this?

谢谢!

edit:简单说一下,这是一个关于代码质量和可维护性的问题,而不是关于让某些东西工作的问题.上面示例中的代码的行为完全符合我的要求,我只是想让实现更好.另外,我知道 python 是动态类型的,我在上面的例子中以静态类型的方式使用它,但我在静态类型语言中也有这个问题,并且想要一个我可以在任何语言中使用的解决方案.此外,对于这个项目,我们决定使用 MyPy 对代码库进行类型检查,并尝试以更强类型的方式使用东西来避免错误.

edit: Just to make it clear, this is a question about code quality and maintainability, and not about getting something to work. The code in the above examples behave exactly how I want them to, I just want to make the implementation better. Also, I am aware that python is dynamically typed, and I have used it in a statically typed way in the above examples, but I also have this problem in statically typed languages and would like a solution that I can use in any language. Also, for this project, we have decided to type check the code base with MyPy, and are trying to use things in a more strongly typed way to try and avoid bugs.

推荐答案

您可以通过创建接口来从具体类中抽象依赖项来实现此目的.因此,不是将 Crane 传递到 Axis,而是传递 Crane 子类的 Moveable.现在,您可以创建不需要 Cranes 的单独测试存根,并且不关心您使用的是 Moveable Cranes 的事实.

You do this by creating interfaces to abstract the dependencies from the concrete class. So instead of passing a Crane into the Axis, you pass in a Moveable in which Crane subclasses. Now you can create separate test stubs that don't require Cranes, and don't care about the fact you are using Moveable Cranes.

为了使其在语言上更加分离,我将 CraneStatus 更改为 MoveableStatus,将 CraneMode 更改为 MoveableMode.

To make it more linguistically separated, I'd change CraneStatus to MoveableStatus and CraneMode to MoveableMode.

这篇关于当属性的方法需要修改所属类的状态时,如何将类与其属性解耦?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆