Dove dovrei utilizzare le classi astratte e le interfacce per un client API REST?

3

Sto provando a scrivere un client API REST per esercitarti e ho difficoltà a capire come si stende il progetto.

L'approccio che sto prendendo adesso ha Actions , DomainObjects , Requests , e una classe che si occupa di autorizzazioni e intestazioni e tale ( CoreClient ) che espone un generico DoPut<T>(string address,T item) e verbi simili .

Esempi

Action : UpdateRecordCode (contiene la logica)

DomainObject : RecordCode (entità)

Request : UpdateRecordCodeRequest (entità, contiene un ID record e un elenco di cose da aggiungere ad esso)

Domanda 1 : Come determinare quali classi devono implementare le interfacce e quali interfacce dovrei avere in primo luogo e quali dovrebbero ereditare?

La mia ipotesi migliore è che Actions debba ereditare da un ActionBase perché ogni Action è fondamentalmente un tipo di ActionBase . DomainObjects dovrebbe probabilmente implementare qualcosa in modo che siano coerenti, ma non sono sicuro di cosa.

La parte più difficile è Requests ; idealmente, mi piacerebbe che altre parti dell'applicazione gestissero le richieste in generale, in modo tale che, nell'interfaccia utente, potessi avere un elenco di richieste che un utente potrebbe scegliere. Ho provato questo in entrambi i modi, ma continuiamo a tornare a una lezione concreta in quanto entrambi si sentono strani. Le interfacce, ad esempio, finiscono per dover dire qualcosa come IRequest<UpdateRecordCode<UpdateRecordCodeRequest>> , il che sembra sciocco.

Domanda 2 : sto sbagliando? Mi sento come se tutti avessero scritto una dozzina di client API - Ho scritto un numero con una pianificazione minima - ma sto provando a farlo "bene" questa volta ed è, beh, difficile.

So che ci sono molti duplicati dell '"Interfaccia o classe astratta?" domanda, ma penso che questo sia distinto da quelli in quanto riguarda il modo in cui lavorano insieme piuttosto che semplicemente "X o Y?"

    
posta William - Rem 01.03.2017 - 21:32
fonte

2 risposte

1

Gli architetti over-engineering non saranno d'accordo con me, ma in un linguaggio come C #, le interfacce esistono solo per consentire l'ereditarietà multipla, e lo fanno male, senza alcuna implementazione. Cioè, se riesci a trovare un codice di implementazione comune, è meglio usare una classe base anziché un'interfaccia, tranne quando hai bisogno di qualche forma di ereditarietà multipla. Poiché C # consente solo l'ereditarietà multipla sotto forma di interfacce, sei bloccato con un'interfaccia.

Nel caso del tuo esempio specifico, non hai spiegato perché il design garantisce interfacce o classi. Quella che tu chiami "richiesta" sembra essere una combinazione di un'azione (verbi) e parametri (nomi). Questo può essere espresso in molti modi, ma il più logico per me sarebbe un oggetto composito contenente l'azione e i parametri. L'azione non deve essere specificata utilizzando una classe o un'interfaccia.

    
risposta data 06.03.2017 - 18:25
fonte
3

Come ha detto @Frank Hileman, le interfacce in C # non hanno implementazione e non puoi ereditare da più di una classe concreta / astratta.

Tuttavia, qui c'è più di usare le interfacce che l'ereditarietà.

Test: l'uso delle interfacce nella tua applicazione ti permetterà di "prendere in giro" un oggetto che è necessario da un altro.

Ad esempio, se si desidera testare una classe che manipola il nome di una persona in modi diversi, non si vorrebbe creare un database con tabelle e dati per memorizzare persone (con ogni variazione dei dati di test richiesti), quindi chiamare attraverso tutto il codice richiesto per recuperare quelle Persone, quindi passare quelle Persone alla classe che stai testando una per una.

Ciò non solo testerebbe la manipolazione dei dati della persona, ma tutto il codice necessario per connettersi con i dati (inclusa l'autenticazione ecc.)

Invece, vorrai che la classe che stai testando accetti un oggetto che implementa l'interfaccia per una persona. Ora puoi semplicemente creare un nuovo oggetto MyPerson: IPerson personalizzato, popolarlo con tutti i diversi valori che desideri testare, passandoli ogni volta per il test.

Garantire un oggetto implementa proprietà e / o funzionalità - indipendentemente da cosa sia effettivamente quell'oggetto:

Dove potresti voler passare un oggetto che presenta determinate funzionalità su cui potresti voler agire, non devi dover digitare type-check per ottenere i metodi o le proprietà che desideri (che potresti avere fare con classi concrete che ereditano solo da una classe astratta).

Indipendentemente dall'oggetto sottostante, le sue funzionalità esposte tramite un'interfaccia sono immediatamente ottenibili e utilizzabili.

Riutilizzo del codice:

L'uso di classi astratte per funzionalità comuni riduce il volume del codice e la possibilità di errori.

Il tuo ActionBase potrebbe avere metodi comuni come Create<T> , <T>Find(int) , List<T>Get , Update<T> e Delete<T> . Puoi implementarli in una singola classe astratta utilizzando i generici, quindi rimuovi tutto il codice dalla classe Action che normalmente manterrà tutto ( Action: ActionBase<T> ).

Riguardo alle tue entità, ancora una volta, una classe astratta terrebbe le proprietà comuni in uso. Questi possono essere l'identificatore (se il tipo di dati è comune in tutte le entità), un flag Status, DateCreated, DateModified, ecc. Se usato in tutte le entità.

    
risposta data 28.03.2017 - 20:33
fonte