I am working with a desktop app that should send and receive commands via serial with a firmware being programmed by my coworker.
We have devised a protocol where command types are represented by ASCII letters, each command type might contain a payload (which is arbitrary between command types, but fixed for each type), and commands are wrapped in square brackets. For example, we have the following commmands:
[S1234]
-> Sent vrom the PC to the device to store a new serial number, or from the device to the PC to inform the current serial number (sort of a getter/setter command);[R]
- Sent from the PC to the device to ask for a "get serial" command;[A12]
- Sent from the the device to the PC to inform a new ADC reading;[B]
- Sent from PC to device to ask for a battery charge;So I have a class that receives and parses the incoming bytes, and each time a command is successfully parsed, an event is raised, with the following tentative signature:
internal event EventHandler<SerialCommand> CommandReceived;
Where SerialCommand
would have different subtypes: BatteryCommand, AdcCommand, SerialCommand, and others. Each command type is to be associated with its respective "char code".
My question is: how should the client code use this? For example, the current implementation for when I receive a command has a switch/case with hard-coded char literals, which I find very fragile and ugly:
void CommandReceivedHandler(object sender, SerialCommand command)
{
switch (command.Code)
{
case 'A':
int value= Convert.ToInt32(command.Value);
_frameStreamer.AddFrame(new Frame<int>(new[] { value}));
break;
case 'B':
BatteryLevel= (int)command.Value;
break;
case 'D':
DoSomething((byte)command.Value);
break;
case 'S':
SerialNumber = (int)command.Value;
break;
}
}
Currently, these "char codes" are spread around a bunch of classes, and if I ever need to change a given code, I would need to look around for every occurence (shotgun surgery anti-pattern).
What I need to do are two things:
CommandReceived
event consumer) preferrably without the switch/case statement.You can try something like this:
public abstract class BaseCommand
{
//Code not needed at all, because logic encapsulated into command
//public char Code { get; set; }
public abstract void Action(IClient client);
}
public abstract class BaseCommand<T> : BaseCommand
{
public T value { get; set; }
}
public class CommandA : BaseCommand<int>
{
public override void Action(IClient client)
{
client.someInt = value * 2;
}
}
public class CommandB : BaseCommand<string>
{
public override void Action(IClient client)
{
client.someString = value.Trim();
}
}
public interface IClient
{
void CommandReceivedHandler(object sender, BaseCommand command);
int someInt { get; set; }
string someString { get; set; }
}
public class Client : IClient
{
public void CommandReceivedHandler(object sender, BaseCommand command)
{
command.Action(this);
}
public int someInt { get; set; }
public string someString { get; set; }
}