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

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

问题描述

当属性的方法需要修改所属类的状态时,如何将类与其属性分离?另外,如何重新设计体系结构,以免出现问题?

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_x move_y 中重复了用于移动每个轴的业务逻辑. Crane 的> move_z 方法.这是代码重复,因此此功能被封装在Axis类中,然后Crane类由其中的3个组成,现在只需将 move_x move_y 委派给他们,和 move_z 方法添加到相应的Axis.即:

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,但是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.

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

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):  

CraneStatus CraneMode 是枚举.它与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的变化很敏感.依赖关系有两个方向,起重机取决于轴,而坐标轴取决于起重机.

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?

如果我确实想使轴与起重机分离,那么解决这个问题的最佳方法是什么?

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的单独的测试存根,也不必担心您正在使用可移动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天全站免登陆