0

I need to create some commands that share the method Execute() and a property that gets the result of such execution: List<IResponse>. I have the following interface:

public interface Command
{
    public IEnumerable<IResponse> Responses;
    void Execute();
}

Each class that implements command needs different inputs (parameters), for example:

  • GetDeviceNames command needs to know the Ids of the devices.
  • SendDeviceData command needs to know the device Id, its Name, its Type, its Status, etc.

At the end the user should use the application sending a json to incapsulate the parameters of each command, something like:

$ application GetDeviceNames --parameters '{"ids" = [0, 1, 2, 3]}'
$ application SendDeviceData --parameters '{"id" = 0, "Name" = "Device1", ...}'

So, my question is, should the command classes receive the parameters as objects, like this?:

public SendDeviceData : ICommand
{
    private SendDeviceDataParameters parameters;
    public IEnumerable<IResponse> Responses;

    public SendDeviceData(SendDeviceDataParameters parameters){/* ... */}
    public void Execute(){/* ... /*}
}

So the client uses like:

// parameters object might also be deserialized from json
SendDeviceDataParameters parameters = new SendDeviceDataParameters()
{
    Id = 0,
    Name = "Device1",
    ...
}

SendDeviceData command = new SendDeviceData(parameters);
command.Execute();

Or should the command classes set the parameters as properties, like this?:

public SendDeviceData : ICommand
{
    public int Id { get; set; }
    public string Name { get; set; }
    ...
    public IEnumerable<IResponse> Responses;

    public SendDeviceData(){/* ... */}
    public void Execute(){/* ... */}
}

So the client uses like:

// The values of parameters might be gotten from json previously
SendDeviceData command = new SendDeviceData()
{
    Id = 0,
    Name = "Device1",
    ...
};

command.Execute();

I am confused because I see advantages and disadvantages for each one:

  • If I pass the parameters as an object:
    • Advantages
      • It is easier to serialize to or deserialize from json because parameter classes can be used.
    • Disadvantages
      • Some kind of details are exposed to the client that create a command.
      • The need of creating a parameter class for each command because each command require a different type of input (parameters).
  • If I pass the parameters as properties:
    • Advantages
      • Less details about how commands works are exposed to client.
    • Disadvantages
      • Serialize to or deserialize from json would require create an "intermediate" class.

What are your thoughts about it?

3
  • 3
    Which advantages do you want, and which do you want to avoid? Sep 1, 2021 at 16:40
  • 1
    "I need to create some commands that share the method Execute() and a property that gets the result of such execution" - As an aside, I really don't like this. Why can't Execute just return the result? Having it as a property gives you all sorts of bad temporal coupling. What happens if you access the result before calling Execute? What happens if you call Execute again? What if you do it twice, concurrently? Sep 1, 2021 at 19:49
  • @SebastianRedl IEnumerable<IResponse> Responses might suggest that the command tracks its history of responses, at which point it can make sense to fetch responses separately from triggering the command itself. But I do agree with you if there is a strict one execution -> one response interaction between the command and its consumer.
    – Flater
    Sep 3, 2021 at 10:02

2 Answers 2

3

If you are going to do this

    // parameters object created or deserialized from json
    SendDeviceDataParameters parameters = /* ... */

    // set params & call Execute in the same place or component:  
    SendDeviceData command = new SendDeviceData(parameters);
    command.Execute();

then it doesn't really matter, and your SendDeviceData doesn't really have to implement the Command interface (your client code isn't actually working with the abstract command type; your component already depends on the concrete SendDeviceData type). So if that's how and where it's used, do what's more convenient.

This setup may make prefect sense for what you're doing; I'm not saying that the design below is better and that you should switch to it, just that it's an option if you have similar constraints. It's another tool in your designer toolbox.

If you are doing something like this instead:

    // In a lower-level layer where you have access to these parameters:
    SendDeviceDataParameters parameters = new SendDeviceDataParameters()
    {
        Id = 0,
        Name = "Device1",
        ...
    }
    SendDeviceData command = new SendDeviceData(parameters);
    
    // pass 'command' to some higher level layer as Command
    
    // --------------------------------------------------------------
    // SOMEWHERE ELSE, in some higher level class that doesn't know 
    // about the concrete command classes: 
    
    Command _command;  // private property, set in the constructor
    // ...
    
    // in some method:
    _command.Execute();

    // ---------------------------------------------
    // in Main() or some other "root" component that knows about both:

    Command cmd = commandDeserializer.CreateFromJson(commandJson);
    var useCaseOfSomeSort = new HighLvlClass(cmd);
    useCaseOfSomeSort.DoSomething(...);  // eventually calls _command.Execute()

    // or something along those lines...

then the Command interface serves its purpose as a polymorphic interface, letting this class focus on the high level logic. For this to work, this class cannot know about the concrete Command derivatives, and has to "outsource" the responsibility of filling in the parameters to some other (lower level) code that knows how to do it.

So you'd pass parameters specific to a concrete command in a lower level layer either as an object, or if more convenient, as by setting a bunch of properties (because of serialization/deserialization, or to interop with some framework) - and leave these unavailable to the higher level component (the Command interface doesn't expose them). They are encapsulated within the command, as is any code that makes direct use of them.

If you also have more general parameters that don't depend on the concrete type of the command and can be provided at the call site by the high level component, you can pass those as parameters of the Execute() method (change the Command interface to support that). These could, for example, be parameters that are only available at runtime (perhaps dependent on user input), or objects or lambdas that produce side effects (e.g. you could pass in an Action<T> that will call some local function or invoke a method on a caller's dependency at a specific point during the Execute() method).

1

Merging the executor and the data into a single class is an older-style approach that is consistent with earlier notions about encapsulation. The data and logic go together; everything in one place. This works well in a fat-client situation where your logic is literally contained in a single program. Today, you can still see this pattern at work in PowerShell Cmdlets written in c#. It's a pluggable architecture, meant for quick development, and it is very helpful to put everything related to a plugin in a single class.

Using a separate DTO class is a more modern approach that came about because of the need for distributed processing. A DTO class can be used to serialize and deserialize requests, which is a separate concern from actually processing them. A web client, for example, would need the DTO and not the processor.

The DTO style also works well with inversion of control. You can inject the data processor-- meaning that you can mock its behavior for unit testing-- while keeping the DTOs separate, since, as dumb data containers, they do not need to be mocked.

A third advantage of the DTO style is that you can make your DTOs immutable and your executors stateless, which allows you to write logic in a more functional style. This has numerous benefits, including the virtual elimination of certain kinds of threading issues; if you never mutate anything, you never have to lock anything. Not only does this get rid of many hard-to-find bugs, but it can also improve performance, and simplify your logic.

Both approaches have their use. If I were writing a web application, I'd probably lean toward the latter.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.