2016-04-28 19 views
24

Sto studiando i principi SOLID e ho una domanda sulla gestione delle dipendenze in relazione alle interfacce.Puoi avere un'interfaccia dipendente da una classe?

Un esempio dal libro che sto leggendo (Codice Adaptive tramite C# da Gary McLean Hall) mostra una classe TradeProcessor che otterrà i dati commerciali, di processo, e memorizzare nel database. I dati commerciali sono modellati da una classe chiamata TradeRecord. Una classe TradeParser gestirà la conversione dei dati commerciali ricevuti in una o più istanze di TradeRecord. La classe TradeProcessor fa riferimento solo all'interfaccia ITradeParser in modo che non dipenda dall'implementazione TradeParser.

L'autore ha il metodo Parse (nell'interfaccia ITradeParser) restituisce una raccolta IEnumerable<TradeRecord> che contiene i dati commerciali elaborati. Ciò non significa che ITradeParser ora dipende dalla classe TradeRecord?

L'autore non ha dovuto fare qualcosa come fare un'interfaccia ITradeRecord e avere Parse restituire una raccolta di istanze ITradeRecord? O mi manca qualcosa di importante?

Ecco il codice (l'implementazione di TradeRecord è irrilevante per cui viene omesso):

TradeProcessor.cs

public class TradeProcessor 
{ 
    private readonly ITradeParser tradeParser; 

    public TradeProcessor(ITradeParser tradeParser) 
    { 
     this.tradeParser = tradeParser; 
    } 

    public void ProcessTrades() 
    { 
     IEnumerable<string> tradeData = "Simulated trade data..." 
     var trades = tradeParser.Parse(tradeData); 

     // Do something with the parsed data... 
    } 
} 

ITradeParser.cs

public interface ITradeParser 
{ 
    IEnumerable<TradeRecord> Parse(IEnumerable<string> tradeData); 
} 
+0

Nota anche: l'autore sostiene di mantenere le interfacce e le loro implementazioni in un assieme separato (ad esempio il modello di scala). Se seguo quel modello, 'TradeRecord' dovrebbe essere presente in entrambi gli assembly o dovrei avere un riferimento dall'assembly di implementazione all'assembly di implementazione (creando così un riferimento circolare). –

+1

In realtà l'implementazione di TradeRecord è rilevante. Se questa è pura classe di dati (contiene solo proprietà senza alcuna logica), quindi non ci sono molte ragioni per intorciare l'interfaccia per esso. – Evk

+1

Penso che sia perché TradeRecord è solo un semplice oggetto DTO. Ofcourse puoi creare un'interfaccia per questa classe ma creeresti interfacce per ogni altra classe che crei? Puoi trattare la tua classe TradeRecord come un contratto. – MistyK

risposta

40

Questa è una buona domanda che va nella compromesso tra purezza e praticità.

Sì, con principal puro, è possibile affermare che ITradeParser.Parse deve restituire una raccolta di interfacce ITraceRecord. Dopo tutto, perché legarti ad una specifica implementazione?

Tuttavia, è possibile continuare. Dovresti accettare uno IEnumerable<string>? O dovresti avere una specie di ITextContainer? I32bitNumeric anziché int? Questo è reductio ad absurdum, naturalmente, ma mostra che noi, ad un certo punto, raggiungiamo un punto in cui stiamo lavorando su qualcosa di, un oggetto concreto (numero, stringa, TraceRecord, qualunque), non un astrazione.

Ciò evidenzia anche il motivo per cui utilizziamo le interfacce in primo luogo, ovvero la definizione di contratti per la logica e la funzionalità. Un ITradeProcessor è un contratto per un'implementazione sconosciuta che può essere sostituita o aggiornata. Un TradeRecord non è un contratto per l'implementazione, è è l'implementazione. Se si tratta di un oggetto DTO, quale sembra essere, non ci sarebbe alcuna differenza tra l'interfaccia e l'implementazione, il che significa che non c'è un vero scopo nella definizione di questo contratto - è implicito nella classe concreta.

+0

Eccellentemente spiegato, questo ha perfettamente senso ora. Come rapido accostamento: quando si implementa il modello Stariway, la classe 'TradeRecord' risiederà nello stesso assembly delle interfacce in modo che le interfacce e le implementazioni possano fare riferimento a' TradeRecord' (come menzionato da @Evk in un commento)? –

+1

Sì, penso che si sarebbero seduti entrambi nello stesso assemblea. Questo assembly, solitamente chiamato "Comune" o "Condiviso" o "Interfacce", contiene i contratti pubblici condivisi esposti e implementati nel codice. Non è possibile consumare o implementare ITradeProcessor senza conoscere TradeRecord. –

10

L'autore ha il metodo Parse (nell'interfaccia ITradeParser) restituisce una raccolta IEnumerable che contiene i dati commerciali elaborati.

Ciò non significa che ITradeParser ora dipende dalla classe TradeRecord?

Sì, ITradeParser è ora saldamente accoppiato con TradeRecord. Dato l'approccio più accademico di questa domanda, posso vedere da dove vieni. Ma cos'è TradeRecord? Un record, per definizione, è in genere un dato di dati semplice e non intelligente (talvolta chiamato POCO, DTO o Modello).

A un certo punto, il potenziale guadagno di astrazione è meno prezioso delle complessità che causa. Questo approccio è abbastanza comune nella pratica: i modelli (come li definisco) sono tipi sigillati che fluiscono attraverso gli strati di un'applicazione. I livelli che agiscono sui modelli sono astratti alle interfacce, in modo che ogni livello possa essere deriso e testato separatamente.

Ad esempio, un'applicazione client può disporre di un livello View, ViewModel e Repository. Ogni livello sa come lavorare con il tipo di record concreto. Ma il ViewModel potrebbe essere cablato per funzionare con un IRepository fittizio, che costruisce i tipi concreti con dati codificati e codificati. A questo punto non c'è alcun vantaggio per un IModel astratto: ha solo dati dritti.

+0

Grazie per la risposta. Sapevo che probabilmente lo stavo pensando troppo e questo era il caso. –

+3

Non direi che sta pensando troppo. È più di mettere la teoria alla pratica e trovare i posti in un livello applicativo che traggono maggiori benefici dall'astrazione. – Jonesopolis

Problemi correlati