Come progettare un emulatore di comunicazione scriptable?

1

Requisiti: Abbiamo bisogno di uno strumento che simuli un dispositivo hardware che comunica tramite RS232 o TCP / IP per permetterci di testare la nostra applicazione principale che comunicherà con il dispositivo.

Flusso attuale:

  • L'utente carica lo script
  • Scrittura di script nei comandi
  • L'utente esegue lo script
  • Esegui comandi

Script / comandi (semplificati per la discussione):

  • Connetti RS232 = > RS232ConnectCommand
  • Connetti TCP / IP = > TcpIpConnectCommand
  • Invia dati = > SendCommand
  • Ricevi dati = > ReceiveCommand
  • Disconnect = > DisconnectCommand

Tutti i comandi implementano l'interfaccia ICommand. Il comando runner esegue semplicemente una sequenza di implementazioni ICommand in modo sequenziale così ICommand deve avere un'esposizione Execute, pseudo codice:

  • void Execute (contesto ICommunicator)

Il metodo Execute prende un argomento di contesto che consente alle implementazioni di comando di eseguire ciò che devono fare. Ad esempio, SendCommand chiamerà context.Send, ecc.

Il problema RS232ConnectCommand e TcpIpConnectCommand devono istanziare il contesto da utilizzare con i comandi successivi. Come gestisci questo elegantemente?

Soluzione 1: Cambia il metodo ICommand Execute in:

  • Esegui ICommunicator (contesto ICommunicator)

Mentre funzionerà sembra un odore di codice. Tutti i comandi ora devono restituire il contesto che per tutti i comandi tranne quelli di connessione sarà lo stesso contesto in cui è passato.

Soluzione 2: Creare un ICommunicatorWrapper (ICommunicationBroker?) Che segue lo schema del decoratore e decora ICommunicator. Introduce una nuova esposizione:

  • void SetCommunicator (comunicatore ICommunicator)

E ICommand è cambiato per usare il wrapper:

  • void Execute (contesto ICommunicationWrapper)

Sembra una soluzione più pulita.

Domanda

È un buon design? Sono sulla strada giusta?

    
posta Hawk 26.03.2014 - 03:49
fonte

2 risposte

1

Penso che la soluzione 2 sia la soluzione migliore. Si vuole evitare di trattare i comandi di connessione come casi speciali. Ciò aggiungerebbe complessità al motore di script e / o al linguaggio di script. I tuoi comandi dovrebbero ricevere un contesto di esecuzione generico (o stato di script).

Qualche codice psuedo potrebbe essere:

interface ICommand {
    void Execute(IScriptContext context);
}

interface IScriptContext {
    ICommunicator Channel { get; set; }
}

interface ICommunicator{
    int Read(byte[] buffer, int offset, int count);
    int Write(byte[] buffer, int offset, int count);
    int GetBytesAvailable();
}

class RS232CommunicationChannel : ICommunicator...
class TcpIpCommunicationChannel : ICommunicator...
class SharedMemoryCommunicationChannel : ICommunicator...

class ConnectRS232Command : ICommand {
    void Execute(IScriptContext context) {
        context.CommunicationChannel = new RS232CommunicationChannel();
    }
}

Il ciclo di esecuzione è quindi molto semplice:

IList<ICommand> commandList = LoadCommands();
IScriptContext context = new ScriptContextImpl();
foreach(ICommand cmd in commandList) {
    cmd.Execute(context)
}
    
risposta data 21.11.2014 - 19:15
fonte
0

Sebbene io sia per lo più ambivalente nei confronti dei tuoi due schemi di progettazione di cui sopra, tenderei ad orientarmi maggiormente verso la soluzione 1. Alcuni dei miei ragionamenti sono quindi:

Hai una chiara separazione di preoccupazioni usando Execute (o qualsiasi comando) con un parametro del contesto di comunicazione. Mostra che ogni Execute ha una dipendenza da un contesto che non possiede o gestisce. Quello che fai in Execute interagisce con il contesto di comunicazione nello stesso modo (cioè, invia caratteri, riceve caratteri, metti in pausa la trasmissione, ecc.). Questo ti mostrerà quale interfaccia richiede il tuo contesto di comunicazione. Puoi riutilizzare l'oggetto con un diverso contesto di comunicazione semplicemente richiamandolo di nuovo con un oggetto diverso.

Puoi scambiare il tuo contesto di comunicazione con qualsiasi altra cosa che supporti l'interfaccia. Non c'è nulla di male se il tuo oggetto supporta più funzionalità, ma il tuo oggetto deve supportare i minimi. Significa anche che se si desidera aggiungere qualsiasi altro protocollo di comunicazione (USB, UDP, eSATA, SCSI) in un secondo momento, dovrebbe essere molto semplice.

È possibile creare un metodo factory per ciascuno dei contesti di comunicazione. Ciò sarà utile per i contesti che possono essere multi-threaded poiché è possibile avviare un thread separato per ogni istanza come parte della rotazione dell'istanza. Per quei contesti che non supportano il multi-threading (ad es., Seriale), è anche possibile creare una sola istanza per ogni porta seriale. La fabbrica potrebbe tenere traccia del "pool" delle porte seriali disponibili in modo da non riutilizzarne una già in uso.

Infine, questo segue una metodologia ampiamente conosciuta e supportata per l'implementazione. È (IMO) un'implementazione molto più gestibile.

    
risposta data 26.03.2014 - 06:29
fonte

Leggi altre domande sui tag