How can I decouple them?

32 views Asked by At

I'm working on a simple key press assistant, and I have a CommandExecutor and a Script Loader. My interface for the script executor is defined like this:

class CommandExecutor(ABC):
    @abstractmethod
    def execute(self, context: ScriptInfo, arg: str) -> None: ...


class ScriptLoader(ABC):
    @abstractmethod
    def loads(self, path: Path | str) -> list[ScriptStep]: ...

Here context refers to the current script execution context, and each executor may need some parameters, such as where to click on the screen, or where to click where a certain image appears (for example, the location where 1.png appears), or how much distance to scroll the mouse, etc. So, I set arg as a str to ensure better compatibility.

However, I've encountered a problem: I realized that I can't standardize the parameters. For instance, one of the executor's features requires identifying the coordinates of an image, but it will click at a location offset by (x, y). Therefore, arg should look like this:

@dataclass
class ClickArgWithOffset:
    arg: str # The actual value of arg (the path of the image), like 1.png
    offset_x: int
    offset_y: int

So, in my script loader, I need to transform the parameters into json:

def assemble_wrapper(
            command_code: int, arg: str, jump_to: int, offset_x: int, offset_y: int
        ) -> ScriptStep:
            # NOTE If it’s a single click, double click, right click, or drag, convert the argument into json (because the offset needs to be set)
            if (
                command_code == CommandType.SINGLE_CLICK.value
                or command_code == CommandType.DOUBLE_CLICK.value
                or command_code == CommandType.RIGHT_CLICK.value
                or command_code == CommandType.DRAG.value
            ):
                return ScriptStep(
                    executor_factory.create(CommandType(command_code)),
                    json.dumps(
                        {"arg": arg, "offset_x": offset_x, "offset_y": offset_y}
                    ),
                    jump_to,
                )

            if (
                command_code == CommandType.MOVE.value
                or command_code == CommandType.SCROLL.value
            ):
                x, y = map(int, arg.split(","))
                return ScriptStep(
                    executor_factory.create(CommandType(command_code)),
                    json.dumps({'x': x, 'y': y, 'duration': 0.2}),
                    jump_to,
                )

            return ScriptStep(
                executor_factory.create(CommandType(command_code)), arg, jump_to
            )

Clearly, my Script Loader and my CommandExecutor are now coupled, and what's worse, they are coupled to the specific implementation of the CommandExecutor. This is because if the parameter is a str, how to parse the parameter is done by the specific implementation of the CommandExecutor – in my project, this is implemented by a simple package (the simple implementation of the CommandExecutor). Therefore, my Excel Script Loader must know how the Simple CommandExecutor parses arg. This clearly brings a bad experience.

In my project, you can see the specifics in the executor.simple and script_loader.excel.

My question is, how can I decouple them?

I have a vague idea of abstracting the instruction loading as well, and then let the script loader handle the implementation of loading each type of instruction (this naturally allows for obtaining the return values). After that, it should just be a matter of standardizing the required parameters for each type of instruction, right?

In other words, do I need to set a standard? Both the script loader and the command executor would implement this standard? They would only need to focus on the standard, not on each other. I think this approach might be worth trying.

0

There are 0 answers