Command design pipeline question - where to place validation?

5

Ecco la situazione:

Ho un database e una classe che è fondamentalmente una rappresentazione di un insieme di campi di diverse tabelle al suo interno. Chiamiamolo Messaggio. "Message" non conosce il database e ogni volta che ho bisogno di sincronizzarlo su DB, uso funzioni separate accettando istanze di Message per farlo.

Ogni volta che ho bisogno di fare qualche operazione che comporta la sincronizzazione dei dati da "Messaggio" al database, Non chiamo semplicemente le funzioni Modifier1 / Modifier2 perché il più delle volte, ho bisogno di passare o rollback dell'intera sequenza. (di solito quando si verifica un errore ORA-DB)

Quindi, ho una coda di azioni configurate:

std::list<std::function<DBType>>

In realtà esiste una classe wrapper attorno a questa coda che riceve azioni tramite il meccanismo segnali / slot ed esegue l'intero pacchetto di azioni accumulate su:

Execute();

Quindi, è così: se ho bisogno di aggiornare il database in qualche parte di codice:

   // sending update #1
   emit dbAction(std::bind<CREATE SOME FUNCTOR HERE>);
   // sending update #2
   emit dbAction(std::bind<CREATE SOME OTHER FUNCTOR HERE>;
   // flushing the queue
   emit flushDb();

le azioni vengono eseguite, tutti sono felici. Fine della storia. Fino ad ora quello è. Ora, ho capito che devo avere una serie di precondizioni su "Messaggio" che impedirà il passaggio di queste azioni.

Opzioni disponibili:

1) Per inserire il codice di verifica nelle funzioni chiamate. Ma le condizioni non hanno nulla a che fare con esse e dipendono interamente dal contesto esterno.

2) Per consentire all'utente della coda di verificare le condizioni nel codice esterno. Ma siamo realistici - anche se io sono l'utente indicato, ciò che non è applicato, è facilmente dimenticato.

3) Utilizzare alcune classi per emettere queste azioni dopo aver controllato le precondizioni sugli argomenti. Ma questo richiederà di aggiornare la sua interfaccia per ogni nuova azione e non permetterà semplicemente di inviare un lambda attraverso.

4) La mia attuale folle soluzione: invece di inviare semplicemente std :: function con argomenti già associati, crea un functor basato su modello che mantiene i parametri accessibili finché non sono effettivamente necessari dalla coda.

//abstract class so that I can store unrelated template instantiations in a single container in my queue
class  IMessageActionDB
{
  public:
    virtual ~IMessageActionDB();
    enum EActionTypes
    {
          noaction = 0,
      action_type_1 = 1,
      //...
      action_type_N = N
    };

    void operator()(DBType db)
    {
        if(!Verify())
            throw exception;
        BindToExecutable();
        calledFunc(db);
    };
     // will bind whatever is stored in MessageActionDB to the calledFunc
    virtual void BindToExecutable() = 0;
    // will check the action with supplied checker functor
    bool Verify() = 0;

    protected:
    std::function<void(DBType)> calledFunc;
};

// templated action that will accept any function/number of parameters for later use
template<class FunctionType, class ...Ts>
 class  MessageActionDB : public IMessageActionDB
{
  public:
    MessageActionDB(FunctionType f, std::tuple<Ts...> t);

    void BindToExecutable();
    {
        //perform template mumbo jumbo to bind func and parameterPack to calledFunc
        calledFunc = bind_tuple(func, parameterPack)
    }
    bool Verify() 
    {
        //call verifiers in order returning if action satisfies or not
        return result;
    };

  private:
     std::tuple<Ts...> parameterPack;
     FunctionType func;
     std::list<std::function<bool(Ts...)>> actionVerifier;
}; 

(Nota che ho ancora dubbi se voglio inserire i parametri di verifica nei parametri del costruttore o lasciarli generati da un singleton factory in un corpo costruttore)

In questo modo, posso assegnare il verificatore all'azione in base al suo tipo EActionTypes e il wrapper della coda può utilizzare la funzione Verifica al momento del richiamo per verificare se l'azione è consentita.

Questo è sano? C'è un modo migliore per inserire il controllo delle precondizioni?

Francamente, la mia soluzione mi sembra imbarazzante, non riesco a scrollarmi di dosso la sensazione di aver fatto qualcosa di completamente sbagliato.

    
posta Zeks 01.08.2015 - 01:26
fonte

1 risposta

1

La tua descrizione assomigliava molto all'approccio funzionale. Ma lascia che provi a iniettare qualche orientamen- to sugli oggetti;)

Da quanto ho capito finora, hai una pipeline semplice che riceve un oggetto Message . Quindi, a seconda di alcune condizioni (o incondizionatamente), alcune azioni del database vengono eseguite e quindi l'intera operazione viene eseguita (o ripristinata).

Quindi, lo vedo così:

Ogni azione, eseguita sul database è un'implementazione Command ( DBCommand ).

Quando hai bisogno di alcune condizioni da applicare all'esecuzione del comando, puoi creare un nuovo comando, che implementa Composito pattern ( ConditionalDbCommand ). Quando viene chiamato ConditionalDbCommand execute() , prima verifica le condizioni e se sono ok, viene chiamato execute() del comando interno.

Anche la pipeline può sembrare un comando. Contiene un elenco di comandi da eseguire e commit / rollback della transazione di wrapping.

    
risposta data 17.11.2015 - 12:21
fonte

Leggi altre domande sui tag