The essence of the question is this:

I have a fat client. I want to manage the operation of this client from the server. The server implements a control panel using the Command pattern, and the client has an extensible set of functions for executing server commands. See the code below.

Suppose the client (the receiver of commands) has a "Run" function, which we will call from the server:

class Receiver { public int Register { get; private set; } public void Run(char operationCode, int operand) { switch (operationCode) { case '+': Register += operand; break; case '-': Register -= operand; break; case '*': Register *= operand; break; case '/': Register /= operand; break; } } } 

Control panel code implemented on the server:

 //"Invoker" (Π˜Π½ΠΈΡ†ΠΈΠ°Ρ‚ΠΎΡ€ (Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚Π΅Π»ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄)) class Invoker { private List<Command> commands = new List<Command>(); private int current = 0; public void StoreCommand(Command command) { commands.Add(command); } public void ExecuteCommand() { commands[current++].Execute(); } public void Undo(int levels) { for (int i = 0; i < levels; i++) if (current > 0) commands[--current].UnExecute(); } public void Redo(int levels) { for (int i = 0; i < levels; i++) if (current < (commands.Count - 1)) commands[current++].Execute(); } } //"Command" (Какая-Ρ‚ΠΎ (абстрактная) ΠΊΠΎΠΌΠ°Π½Π΄Π°) abstract class Command { protected dynamic receiver; public Command(dynamic receiver) { this.receiver = receiver; } public abstract void Execute(); public abstract void UnExecute(); } //"ConcreteCommand" (Команда (слоТСниС), ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ сСрвСр ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ) class ConcreteCommand : Command { int operand; public ConcreteCommand(Receiver receiver, int operand) : base(receiver) { this.operand = operand; } public override void Execute() { receiver.Run('+', operand); } public override void UnExecute() { receiver.Run('-', operand); } } 

----------

Tell me, please, how best to implement a method call (for example, "Run") on the side of a fat client:


Make a distributed application by placing the following code in a plugin (application extensible):

 class Receiver : MarshalByRefObject { public int Register { get; private set; } public void Run(char operationCode, int operand) { switch (operationCode) { case '+': Register += operand; break; case '-': Register -= operand; break; case '*': Register *= operand; break; case '/': Register /= operand; break; } } } 

, and on the server just make a call:

 public override void Execute() { receiver.Run('+', operand); } 

But then, on the client, you will have to dynamically connect this plugin. And this means that you have to use slow, resource-intensive reflection. If this problem is solved by creating the following shared assembly:

 class ReceiverRef : MarshalByRefObject { public dynamic Ref { get; set; } } 

and having connected it once, make the following method calls on the server:

 public override void Execute() { ReceiverRef receiverRef = new ReceiverRef(); receiverRef.Ref = receiver; receiverRef.Ref.Run('+', operand); } 

then there is a need to connect the server to the client when the client connects to the server and informs it of its IP and port number. That is, they will have to be swapped, because (as far as I know) in distributed applications, the code is executed on the server side, and I need this code (command) to be executed by my thick client. But then the constant waiting for the client to connect the server will greatly consume the battery power of the device on which it is installed.


Send the name of the method being called as a string to the client:

 class ConcreteCommand : Command { int operand; public ConcreteCommand(Server receiver, int operand) : base(receiver) { this.operand = operand; } public override void Execute() { receiver.Send("Receiver.Run", '+', operand); } public override void UnExecute() { receiver.Send("Receiver.Run", '-', operand); } } 

, and in plugins to place the necessary methods (I remind you that the client is extensible). But then again there is a need to use reflection (and, multiple).


Your ideas.


Thank you in advance.

    1 answer 1

    The way messages are transmitted depends on the technology you use for the exchange. Judging by MarshalByRefObject, I assume that you are using Remoting. In this case, I would suggest creating a Data Transfer Object, which would be transferred between services and contain information about the team and the necessary data. If you have any commands that do not fit into the standard model, you can always make a successor with additional parameters.

    To execute commands with the ability to add new ones, look at the Chain Of Responsibility template. Receiver receives an object with a command and sends it through a chain of handlers. The handler to whom this command is intended to execute it. For .net, if you use dependency injection, it is more convenient to use a modified model:

     class CommandDescriptor { public char OpCode { get; set; } public int Operand { get; set; } } interface IProcessor { bool DoIt(command); } class AddCommandProcessor : IProcessor { private readonly _registerHolder; // Π’Π°ΠΆΠ½ΠΎ, registerHolder Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ зарСгистрирован ΠΊΠ°ΠΊ singleton Π² вашСм DI ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»Π»Π΅Ρ€Π΅. public AddCommandProcessor(RegisterHolder registerHolder){ _registerHolder = registerHolder; } public bool DoIt(command){ if(command.OpCode == '+'){ _registerHolder.Register += operand; return true; } return false; } } class Receiver : MarshalByRefObject { private readonly IProcessor[] _processors; // Π’Π°ΡˆΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ зарСгистрированы Π² DI ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»Π»Π΅Ρ€Π΅. Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ сигнатура ΠΌΠ΅Ρ‚ΠΎΠ΄Π° помСняСтся, Π² зависимости ΠΎΡ‚ Ρ‚ΠΎΠ³ΠΎ ΠΊΠ°ΠΊΠΎΠΉ DI Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅. public Receiver(IProcessor[] processors){ _processors = processors } public Run(CommandDescriptor commandDescriptor){ foreach(var processor in _processors){ if(processor.DoIt(commandDescriptor)){ return; } } } } 

    I wrote the code without checking, so it might be necessary to fix it for compilability.