Come parametrizzare i comandi senza che il chiamante "sappia" qualcosa sul comando in anticipo

4

La mia domanda è simile a (ma non esattamente uguale a) questa domanda . Questa domanda è stata anche utile, ma è meno simile alla mia domanda attuale rispetto alla domanda precedente a cui mi sono collegato.

Ho un framework che esegue test automatici. Fondamentalmente, uno "script" XML ha dati su quali metodi chiamare contro la nostra API; il nostro motore esegue i metodi specificati con la riflessione.

A causa di come vengono generati i log, a volte vogliamo sostituire alcune chiamate di metodo con un'altra. (Non c'è davvero alcun modo per aggirare questo). Una fabbrica "sa" quali metodi sono basati su un file XML che contiene la "mappatura".

// Originally we called this a "Command" pattern but it may be more accurate to call it a Strategy
// See if we're supposed to replace this method call with something else
bool wereAnyCommandsRun = await ExecuteCommandsIfThereAreAny(interfaceName, methodName);

// If we're NOT supposed to replace this method call, go ahead and execute it
if (!wereAnyCommandsRun)
{
   // Do the method call
   // ...
}

Facciamo il mapping XML in questo modo:

<map method="CaptureDomain" interface="IFrameworkInterface" command="ApplyXsltCommand"/>

In questo momento i "comandi" (probabilmente con maggiore precisione "Strategie") sono completamente standard - implementano un'interfaccia con un metodo Execute ().

Ora ho bisogno di aggiungere parametri a questo. Non so in anticipo quali parametri avranno bisogno, quindi sto cercando di trovare qualcosa che non sia completamente odioso e fragile. I miei requisiti principali sono che non voglio che il design diventi eccessivamente complicato; inoltre, il punto principale di questo è rendere il mio motore il più possibile "a prova di futuro", quindi voglio assicurarmi che non richieda modifiche se aggiungo o rifattore un metodo o una nuova richiesta (nel senso che il Comando e il suo chiamante devono essere accoppiati liberamente).

Alcune idee che ho avuto e respinto:

  • Accettare un array params e utilizzare reflection per decidere se il chiamante ha trasmesso valori del tipo corretto. Non mi piace però perché aumenta drasticamente l'accoppiamento tra il comando e l'intero punto del meccanismo è quello di ridurre l'accoppiamento.
  • Crea parametro "oggetti" per ogni tipo possibile. Anche questo creerebbe un sacco di "accoppiamenti".
  • Fabbriche parametrizzate

Alcune idee che sto prendendo seriamente in considerazione:

  • Avere un oggetto Contesto come una delle risposte nella domanda che ho collegato a suggerito. Se sto leggendo la domanda che ho collegato correttamente, i vantaggi qui sarebbero che il chiamante non dovrebbe "sapere" nulla sul comando in anticipo. Ci potrebbe essere un calo di prestazioni se finisco per catturare troppe informazioni sul chiamante, ma le prestazioni non sono una considerazione importante qui.
  • Ho un oggetto MethodInfo e informazioni sui parametri per il metodo che sto sostituendo - magari aggiungilo come un terzo "elemento" nella mappatura di Factory. Il primo problema che vedo qui è simile allo svantaggio della soluzione precedente: il fatto che un parametro sia necessario per il metodo chiamato non implica che sia anche necessario per il comando. Inoltre, i miei comandi possono essere chiamati con diversi metodi, quindi è importante che non vi sia alcun accoppiamento tra di essi (quindi è più complicato di un semplice "se si visualizza questo elenco di parametri, utilizzare quel comando"). Questo potrebbe portare ad un eccessivo accoppiamento?
  • Cerca di implementare una sorta di modello di visitatore al posto del modello di strategia. Tuttavia, sono un po 'confuso su come funzionerebbe effettivamente per dire la verità. Sarebbe solo aggiungere complessità inutile o sarebbe davvero una soluzione legittima qui?

Spero che questo non sia troppo ampio o basato sull'opinione pubblica (per favore fammi sapere se è così e io modificherò - Sono decisamente non che provo a chiedere un "gimme teh" codez "tipo question), ma qualcuno vede un problema con queste soluzioni?

    
posta EJoshuaS 31.10.2016 - 19:40
fonte

2 risposte

2

Bene, potresti usare dynamic e ExpandoObject .

_parameterNames = > Nome del parametro

_data = > Dati da passare

private dynamic Create(string[] data)
        {
            dynamic expando = new ExpandoObject();
            for (var i = 0; i < _parameterNames.Length; i++)
            {
                ((IDictionary<string, object>) expando).Add(_parameterNames[i], data[i]);
            }
            return expando;
        }

Questo ti darà un mezzo molto flessibile per passare parametri e dati al tuo 'reflector' per aggiungere tutti i parametri al metodo che viene invocato tramite reflection. Manterrei i parametri per preservare la sanità mentale.

    
risposta data 31.10.2016 - 20:22
fonte
1

Di che tipo di parametri stiamo parlando qui? Sono dipendenze (repository, servizi, adattatori, ecc.) O sono input (ID entità, selezione modalità, ID categoria, ecc.)?

Se sono delle dipendenze, usa un contenitore IoC. Registra le tue dipendenze all'avvio, quindi utilizza il contenitore IoC per risolvere le istanze di comando per te quando necessario.

Se, d'altra parte, i parametri sono input, la risposta di Jon sembra soddisfacente.

    
risposta data 01.11.2016 - 15:31
fonte

Leggi altre domande sui tag