2

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;
  • `[B89] - Sent from device to PC to inform battery charge;
  • Etc.

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:

  • Encapsulate char codes inside the very commands only, instead of client code;
  • Polymorphically execute actions at the client (CommandReceived event consumer) preferrably without the switch/case statement.
1

1 Answer 1

2

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; }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I liked the way you provide the client instance to the command instance, instead of the opposite which is what I have been trying. I believe this is the way the Command Pattern is intended to be used - either by passing a client instance or a client metod to the command.
Actually I think I'll go with a variant where I pass the client via constructor, not via action call. Either way, the collaborations are equivalent. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.