Interfaccia a livello di classe o di funzione?

5

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.)

    
posta Jeremyx 19.11.2013 - 21:13
fonte

3 risposte

1

Penso immediatamente a due modelli che possono essere utili per il tuo caso.

Modello metodo modello

Tutto ciò che è uguale per tutte le implementazioni concrete risiede in una singola classe astratta. Tutto ciò che è più concreto è delegato alle sottoclassi. Se fossi in te, vorrei provare prima questo.

Modello generatore

Se hai bisogno di maggiore flessibilità, ad es. se si utilizza il modello di modello di modello si dovrebbe creare una vasta gerarchia di classi astratte, si può beneficiare del modello di builder. Trasformare il codice imperativo in un oggetto più descrittivo è esattamente ciò che ottieni con questo modello. Tutto ciò che rimane per le applicazioni client è quindi creare e passare una descrizione concreta di ciò che si desidera CRUD dal backing store.

    
risposta data 27.12.2013 - 22:28
fonte
0

Potresti avere più indirezione del necessario. Ad esempio:

public SimpleRecord GetSimpleRecord(int recordID) {
   return getResource(repository => repository.SimpleRecords.Find(recordID));
}

potrebbe semplicemente essere:

public SimpleRecord GetSimpleRecord(int recordID) {
   return repository.SimpleRecords.Find(recordID);
}

Supponendo che il repository soddisfi un contratto di interfaccia come IRepository, puoi semplicemente iniettare un repository che attinge dall'origine dati corretta.

public class DataAccess
{
    IRepository repository;

    public DataAccess(IRepository repository)
    {
        this.repository = repository;
    }

    public SimpleRecord Find(int recordID)
    {
        // etc.
    }
}
    
risposta data 19.11.2013 - 21:42
fonte
0

Perché non usare semplicemente la funzione assert-like?

public T Exists<T>(T val) where T : class
{
    if (val == null)
        throw new DataNotFoundException();
    return val;
}

E poi usalo:

public Person Get(string first, string last) {
   return Exists(repository.People.Find(first, last));
}

Questo probabilmente si applica solo al tuo semplice esempio. Ma non sappiamo quanto siano complesse le tue esigenze se non descrivi i tuoi casi complessi. Penso che ti stai avvicinando a questo problema dal lato negativo. Mentre C # ha poche proprietà funzionali, non è un linguaggio funzionale. Cercare di risolvere i problemi in modo funzionale non sembrerà mai bello.

    
risposta data 27.12.2013 - 23:03
fonte

Leggi altre domande sui tag