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.