Recentemente sono caduto in un pattern in cui sono state definite routine che si basano su un'interfaccia definita da una funzione specificata come parametro per la routine. (La lingua è C #, ma può essere applicata a qualsiasi lingua con funzioni di prima classe.)
Ad esempio, ho un'API che espone le operazioni CRUD contro un backing store. Nel caso di un'operazione GET su una particolare risorsa, posso generalizzare la routine in:
- ottieni la risorsa che stiamo cercando
- restituisce una risposta non trovata se la risorsa non esiste
- restituisce la risorsa se trovata
Quello che ho finito è definire una routine che accetta una funzione di delegato per trovare la risorsa. È questo delegato che definisce il contratto di interfaccia.
Questo capita di funzionare bene per la mia situazione perché le informazioni necessarie per localizzare la risorsa possono variare. Nel mio caso, cerca i dati in un database con le chiavi, ma il tipo e il numero di chiavi possono variare. Posso catturarli in una chiusura nella routine chiamante e soddisfare l'interfaccia della funzione delegata. Ad esempio:
// Locate a simple record that only has one key
public SimpleRecord GetSimpleRecord(int recordID) {
return getResource(repository => repository.SimpleRecords.Find(recordID));
}
// Locate a complex record that has many keys
public ComplexRecord GetComplexRecord(int recordID, int userID, string token) {
return getResource(repository => repository.ComplexRecords.Find(recordID, userID, token));
}
Questo lavoro, ma sembra un mix di OOP e programmazione di stile funzionale. Se ho bisogno di più di un delegato passato, inizia a diventare un po 'disordinato. Alcune routine di cui ho bisogno ovunque ho finito per definire come metodi astratti che tutte le sottoclassi devono implementare. Quindi ho un ibrido.
Questo tipo di tecnica ha un nome o un modello che mi manca? I delegati dovrebbero essere implementati in un'interfaccia di classe definita che viene passata al chiamante?
UPDATE con un esempio più concreto:
Sto cercando di aderire al principale di DRY. Sto parlando di controller in un'applicazione API Web C #. Ogni singola richiesta ha una comunanza che ho implementato in una classe di controller di base:
- Gestisci tutte le eccezioni restituendo il codice di stato HTTP corretto, (404 per le risorse non trovate, 201 per le risorse create, ecc.)
- Esegui il mapping delle entità del database agli e-from oggetti di trasporto dati con cui il client gestisce
Voglio esprimere cosa fare in questa classe base e delegare come alla classe concreate. Il come finisce per essere implementato dalle funzioni delegate. Potrei aver bisogno di ottenere una persona dal database, per nome e cognome, o un ordine di acquisto, con un ID intero. In entrambi i casi, se la risorsa non viene trovata, è necessario restituire un 404. Uno restituisce una persona, una un ordine di acquisto. Come cercarli e come mappare i dati su un oggetto client è diverso.
La funzione base potrebbe essere simile a questa:
T getResource<T> (Func<IRepository, T> find) {
T data = find(getRepository());
if (data == null) {
throw new DataNotFoundException();
}
return data;
}
Ora, nel controller di persona e nel controller dell'ordine di acquisto, non devo ripetere la logica di cosa fare quando la risorsa non viene trovata: implementare semplicemente il delegato di ricerca. (Questo è un semplice esempio senza mappatura, aggiunta, rimozione o altri dettagli che differiscono da risorsa a risorsa).
public Person Get(string first, string last) {
return getResource<Person>(repository => repository.People.Find(first, last));
}
public PurchaseOrder Get(int id) {
return getResource<PurchaseOrder>(repository => repository.POs.Find(id));
}
Si noti come le chiusure di cui sopra gestiscono in modo ordinato il numero e il tipo di parametri variabili per trovare le cose, ma soddisfano l'interfaccia definita dalla funzione di ricerca dei delegati. E 'possibile farlo con le interfacce di classe standard?
(E questa domanda non riguarda il repository, che è risolto con l'integrazione delle dipendenze ed è implementato con Entity Framework.)