samedi 30 octobre 2021

OO-Design: How to make it clear to IDEs which subclass return type a function returns?

I have implemented a protocol in an object oriented way. There are Command and Response classes for each Command and its Response. There is also a Device class, which aids with sending Commands and getting their response.

Now, if I send a command and want to interpret the response I can do:

/** @var Device */
$response = $device->sendCommand(new GetSomethingCommand());
// response can now only be a SomethingResponse object, so I can safely access:
$response->someField;
// But IDEs do not know this,
// so if I want to have auto-completion
// I need to explicitly do an instanceof check:
if($response instanceof SomethingResponse){
    //now I can access $response->someField without the IDE complaining.
    $response->someField;
}

My question is: Is the way I implemented this bad design? Is there anything I can do to make this clearer to IDEs? I want to avoid to constantly do absolutely unnecessary instanceof checks.

Here is the most important content of some classes to understand the structure:

class Device{
    //...   
    public function sendCommand(Command $command): Response{
        $this->deviceConnection->send($command->encode());
        return $command->decode($this->deviceConnection->readUntil());
    }
    //...
}
abstract class Command{
    //...
    
    public function encode(): string{
        //...
    }
    
    abstract public function decode(string $data): Response;
}
class GetSomethingCommand extends Command{
    ///...
    public function decode(string $data): SomethingResponse{
        return new SomethingResponse($data);
    }
}
abstract class Response extends CharacterStream{
    //...
    
    public function __construct(string $data){
        //code calling decode
    }
    
    abstract protected function decode(FieldStream $dataStream);
}
class SomethingResponse extends Response{
    //...
    protected function decode(FieldStream $dataStream){
        //...
    }
}

My idea for a solution

One idea I got while writing this question is to have the sendCommand functionality in the Command class. I have the following issues with this though:

  1. This would require passing the Device or DeviceConnection object to this send function, which makes the raw protocol implementation which only worked with strings up until now dependent on the Device classes/interfaces (which implement specific communication).
  2. It also would be a lot (well 3 lines) of boilerplate code in each Command class.

That's why I really don't like this idea, but maybe it is actually the proper way to do it?

This is how it would look like I think:

//in the command class:
abstract class Command{
    //...
    abstract public function send(DeviceConnection $deviceConnection): Response{
        $deviceConnection->send($this->encode());
        return $this->decode($deviceConnection->readUntil());
    }
    //...
}
//in each command class we need to overload the return type:
class GetSomethingCommand extends Command{
    ///...
    public function send(DeviceConnection $deviceConnection): SomethingResponse{
        return parent::send($deviceConnection);
    }
    //...
}

Aucun commentaire:

Enregistrer un commentaire