Gestore di comandi che esegue comandi con dipendenze diverse

3

In pratica, ho una classe Engine che riceve un comando come stringa dall'input e lo passa a una classe CommandHandler che esegue il comando appropriato.

CommandHandler passa la stringa a CommandFactory per ottenere il comando e chiama il metodo Execute () del comando, ma il problema è che ogni comando dipende da diverse classi da eseguire correttamente. Ad esempio, un comando necessita di IOutputWriter per scrivere qualcosa, l'altro ha bisogno di IBuldingFactory per creare un edificio, ecc. Sto usando un reflection nella classe CommandFactory e non posso passare le dipendenze tramite il costruttore usando Activator.CreateInstance () , perché ogni comando ha dipendenze diverse.

La mia architettura attuale è simile a questa:

class Engine()
{
    IData data; // application database
    IInputReader inputReader;
    ICommandHandler commandHandler;

    public void Run()
    {
        string command = inputReader.Read();

        commandHandler.Handle(command, data);
    }
}

class CommandHandler()
{
    ICommandFactory commandFactory();

    public void Handle(string command, IData data)
    {
        string executableCommand = commandFactory().createCommand(command);
        executableCommand.Execute(data);
    }
}

class DisplayDataCommand : ICommand
{
    IOutputWriter outputWriter;        

    public void Execute(IData data)
    {
        outputWriter.Print(data.ToString());
    }
}

class BuildCommand : ICommand
{
    IBuildingFactory buildingFactory;
    string buildingType;

    public void Execute(IData data)
    {
        var building = buildingFactory.createBuilding(buildingType);

        data.AddBuilding(building);
    }
}

Posso avere metodi diversi nel gestore di comando per ogni comando e chiamare il metodo appropriato usando il caso dell'interruttore, ma ciò violerebbe il principio Aperto / Chiuso. Quindi la mia vera domanda è: come implementare ciò senza violare il principio Open / Closed.

    
posta Angel Zapryanov 19.12.2015 - 11:30
fonte

1 risposta

3

Il problema che hai è la conversione della stringa nella classe.

Non c'è molto che puoi fare al riguardo, dato che dovrai sempre avere un factory che sappia analizzare la stringa x nell'oggetto a, b, c da qualche parte.

Ma sposterei la logica al di fuori del gestore comandi, che dovrebbe solo accettare gli oggetti comando. E fai la conversione mentre le stringhe vengono lette nell'applicazione, tramite un repository (il tuo inputReader o IData?) In cui inserisci le tue fabbriche di analisi delle stringhe.

Se permetti il failover, in modo che quando un repository / factory non riesce a gestire una stringa di input, passa alla successiva, che ti darà ulteriore separazione delle preoccupazioni.

Se usi una libreria di serializzazione / deserializzazione DI o object in yoir repo / factories (unity, json.net) che nasconderà alcune delle dichiarazioni reflection / switch dal tuo codice e renderà più ordinato.

Consentire anche il caso in cui un comando non può essere gestito. Questa non è una brutta cosa, dovresti solo essere in grado di gestire i comandi di cui il tuo codice 'sa' e per far funzionare altri programmi con altri.

Addizionalmente, sei sicuro di aver bisogno sia di commandHandler AND command.Execute () che mi sembra che tu debba scegliere l'uno o l'altro. Se vai con i gestori di comandi, hai una o più camme per tipo. Il gestore ha le dipendenze injected e la logica dal metodo execute.

Ciò mantiene il gestore disaccoppiato, in quanto gestisce solo un singolo tipo di comando, è possibile estrarre solo quei tipi da una coda (IData).

Ovviamente se si mantiene execute e si tira un solo tipo (o un insieme di tipi noti) di oggetto si ha lo stesso codice esatto appena organizzato differentemente, ma si può ritagliare la classe del gestore di comandi, come appena chiama execute.

Il vantaggio (se esiste) del gestore è che puoi gestire lo stesso tipo di comando più di un modo. Mentre con execute devi definire un nuovo tipo di comando con gli stessi dati.

esempio:

class Engine()
{
    IData data; // application database
    IInputReader inputReader;
    Dictionary<string,ICommandHandler> commandHandlers;
    public void Run()
    {
        //todo use a DI framework to inject these types 
        commandHandlers = new Dictionary<string,ICommandHandler>();
        commandHandlers.Add("DisplayData", new DisplayDataCommandHandler(new OutputWriter()) );
        commandHandlers.Add("Build", new BuildDataCommandHandler(new Builder()) );

        foreach(var data in this.data.GetCommands())
        {
            if(commandHandlers.Keys.Contains(data.type))
            {
                var command = inputReader.Read(data.type, data.serializedObject);
                //handle the command
                commandHandlers[data.type].Handle(command);
            }
            else
            {
                //some other program will handle these commands
            }
        }
    }
}

public class InputReaderAndFactory : IInputReader
{
    public ICommand GetCommand(string commandType, string commandJson)
    {
        switch (commandType) 
        {
            case "DisplayData" :
                return JsonConvert.DeserializeObject<DisplayDataCommand>(commandJson);
            case "Build" :
                return JsonConvert.DeserializeObject<BuildCommand>(commandJson);
            default :
                return new UnknownCommand(commandJson);
        }
    }
}

class DisplayDataCommandHandler : ICommandHandler
{
    IOutputWriter outputWriter;
    public DisplayDataCommandHandler(IOutputWriter outputWriter)
    {
        this.outputWriter = outputWriter;
    }

    public void Handle(ICommand command)
    {
        var cmd = command as DisplayDataCommand;
        outputWriter.Print(cmd.Data.ToString());
    }
}
class BuildDataCommandHandler : ICommandHandler
{
    IBuilder builder;
    public BuildDataCommandHandler(IBuilder builder)
    {
        this.builder = builder;
    }

    public void Handle(ICommand command)
    {
        var cmd = command as BuildCommand;
        builder.Build(cmd.Data, cmd.MoreData);
    }
}
class DisplayDataCommand : ICommand
{
    public Data Data {get;set;}
}

class BuildCommand : ICommand
{
    public SomeOtherTypeOfData Data {get;set;}

    public MoreData MoreData {get;set;}
}
    
risposta data 19.12.2015 - 12:00
fonte

Leggi altre domande sui tag