Non dichiarare interfacce per oggetti immutabili

27

Non dichiarare interfacce per oggetti immutabili

[EDIT] Dove gli oggetti in questione rappresentano oggetti Data Transfer (DTO) o Plain Old Data (POD)

È una linea guida ragionevole?

Fino ad ora, ho spesso creato interfacce per classi sigillate che sono immutabili (i dati non possono essere modificati). Ho cercato di stare attento a non usare l'interfaccia ovunque mi preoccupassi dell'immutabilità.

Sfortunatamente, l'interfaccia inizia a pervadere il codice (e non è solo il mio codice che mi preoccupa). Si finisce per passare un'interfaccia, e poi si vuole passare ad un codice che vuole davvero dare per scontato che la cosa che gli viene passata sia immutabile.

A causa di questo problema, sto considerando di non dichiarare mai le interfacce per oggetti immutabili.

Questo potrebbe avere ramificazioni rispetto al Test delle unità, ma a parte questo, sembra una linea guida ragionevole?

O c'è un altro schema che dovrei usare per evitare il problema della "diffusione-interfaccia" che sto vedendo?

(Sto usando questi oggetti immutabili per diversi motivi: Principalmente per la sicurezza dei thread poiché scrivo un sacco di codice multi-thread, ma anche perché significa che posso evitare di fare copie difensive di oggetti passati ai metodi. molto più semplice in molti casi quando sai che qualcosa è immutabile, cosa che non ti succede se ti è stata consegnata un'interfaccia, infatti spesso non puoi nemmeno fare una copia difensiva di un oggetto referenziato tramite un'interfaccia se non lo fa t fornire un'operazione di clonazione o qualsiasi modo di serializzarlo ...)

[EDIT]

Per fornire molto più contesto alle mie ragioni per cui voglio rendere immutabili gli oggetti, consulta questo post di Eric Lippert:

link

Vorrei anche sottolineare che sto lavorando con alcuni concetti di livello inferiore qui, come gli oggetti che vengono manipolati / passati in code di lavoro multi-thread. Questi sono essenzialmente DTO.

Anche Joshua Bloch consiglia l'uso di oggetti immutabili nel suo libro Effective Java .

Follow-up

Grazie per il feedback, tutto. Ho deciso di andare avanti e utilizzare questa linea guida per i DTO e i loro simili. Funziona bene finora, ma è stata solo una settimana ... Comunque, sta andando bene.

Ci sono altri problemi relativi a questo che voglio chiedere; in particolare qualcosa che sto definendo "Deep o Shallow Immutability" (nomenclatura che ho rubato da Deep e Shallow cloning) - ma questa è una domanda per un'altra volta.

    
posta Matthew Watson 06.03.2013 - 12:50
fonte

5 risposte

17

Secondo me, la tua regola è buona (o almeno non è male), ma solo a causa della situazione che stai descrivendo. Non direi che sono d'accordo con esso in tutte le situazioni, quindi, dal punto di vista del mio pedante interno, dovrei dire che la tua regola è tecnicamente troppo ampia.

In genere non definiresti oggetti immutabili a meno che non vengano usati essenzialmente come oggetti di trasferimento dati (DTO), il che significa che contengono proprietà dei dati ma molto poca logica e nessuna dipendenza. Se è così, come sembra che sia qui, direi che sei sicuro di usare i tipi concreti direttamente piuttosto che le interfacce.

Sono certo che ci saranno alcuni puristi unit test che non saranno d'accordo, ma a mio parere, le classi DTO possono essere tranquillamente escluse dai test unitari e dalle iniezioni di dipendenza. Non è necessario utilizzare una fabbrica per creare un DTO, poiché non ha dipendenze. Se tutto crea i DTO direttamente come necessario, allora non c'è davvero alcun modo di iniettare un tipo diverso, quindi non c'è bisogno di un'interfaccia. E dal momento che non contengono alcuna logica, non c'è nulla da testare unitamente. Anche se contengono una logica, purché non abbiano dipendenze, allora dovrebbe essere banale testare la logica, se necessario.

Pertanto, ritengo che stabilire una regola secondo cui tutte le classi DTO non devono implementare un'interfaccia, mentre potenzialmente non necessario, non danneggerà la progettazione del software. Poiché hai questo requisito che i dati devono essere immutabili e non puoi applicarli tramite un'interfaccia, direi che è del tutto legittimo stabilire tale regola come standard di codifica.

Il problema più grande, tuttavia, è la necessità di applicare rigorosamente un livello DTO pulito. Finché le classi immutabili senza interfaccia esistono solo nel livello DTO e il livello DTO rimane privo di logica e dipendenze, allora sarai al sicuro. Se inizi a mischiare i tuoi strati, e hai classi senza interfaccia che raddoppiano come classi del livello aziendale, allora penso che inizierai a incontrare molti problemi.

    
risposta data 06.03.2013 - 15:15
fonte
7

Ero solito fare un gran clamore sul rendere il mio codice invulnerabile all'uso improprio. Ho realizzato interfacce di sola lettura per nascondere i membri mutanti, aggiunto molti vincoli alle mie firme generiche, ecc., Ecc. È emerso che per la maggior parte del tempo ho preso decisioni di progettazione perché non mi fidavo dei miei colleghi immaginari. "Forse un giorno assumeranno un nuovo ragazzo di livello base e non saprà che la classe XYZ non può aggiornare DTO ABC. Oh no!" Altre volte mi sono concentrato sul problema sbagliato - ignorando la soluzione ovvia - non vedendo la foresta tra gli alberi.

Non creo più interfacce per i miei DTO. Io lavoro partendo dal presupposto che le persone che toccano il mio codice (principalmente me stesso) sanno cosa è permesso e cosa ha senso. Se continuo a fare lo stesso stupido errore, di solito non provo ad indurire le mie interfacce. Ora passo la maggior parte del mio tempo a cercare di capire perché continuo a fare lo stesso errore. Di solito è perché sto analizzando troppo qualcosa o manca un concetto chiave. Il mio codice è stato molto più facile da lavorare dato che ho rinunciato ad essere paranoico. Finisco anche con meno "quadri" che richiedevano la conoscenza di un insider per lavorare sul sistema.

La mia conclusione è stata trovare la cosa più semplice che funzioni. La maggiore complessità di creare interfacce sicure non fa altro che sprecare tempo di sviluppo e complica un codice altrimenti semplice. Preoccupati di cose come questa quando hai 10.000 sviluppatori che usano le tue librerie. Credimi, ti salverà da tanta tensione inutile.

    
risposta data 06.03.2013 - 22:50
fonte
5

Sembra una buona linea guida, ma per strane ragioni. Ho avuto un certo numero di punti in cui un'interfaccia (o una classe base astratta) fornisce un accesso uniforme a una serie di oggetti immutabili. Le strategie tendono a cadere qui. Gli oggetti di stato tendono a cadere qui. Non penso sia troppo irragionevole modellare un'interfaccia per sembrare immutabile e documentarla come tale nella tua API.

Detto questo, le persone tendono a sovrapporre gli oggetti Plain Old Data (di seguito, POD) e persino le strutture semplici (spesso immutabili). Se il tuo codice non ha alternative sane a una struttura fondamentale, non ha bisogno di un'interfaccia. No, il test unitario non è un motivo sufficiente per cambiare il tuo design (l'accesso al database non è la ragione per cui stai fornendo un'interfaccia, è la flessibilità per i cambiamenti futuri) - non è la fine del mondo se i tuoi test usa quella struttura fondamentale di base così com'è.

    
risposta data 06.03.2013 - 16:34
fonte
2

Code becomes a lot simpler in many cases when you know something is immutable - which you don't if you've been handed an interface.

Non penso che sia una preoccupazione di cui l'implementatore dell'accessor deve preoccuparsi. Se interface X è inteso come immutabile, non è responsabilità dell'installatore dell'interfaccia assicurare che implementino l'interfaccia in modo immutabile?

Tuttavia, nella mia mente, non esiste un'interfaccia immutabile: il contratto di codice specificato da un'interfaccia si applica solo ai metodi esposti di un oggetto e nulla sull'interno di un oggetto.

È molto più comune vedere l'immutabilità implementata come decoratore piuttosto che come interfaccia, ma la fattibilità di tale soluzione dipende molto dalla struttura del tuo oggetto e dalla complessità della tua implementazione.

    
risposta data 06.03.2013 - 16:14
fonte
0

You wind up being passed an interface, and then wanting to pass it to some code that really wants to assume that the thing being passed to it is immutable.

In C #, un metodo che prevede che un oggetto di tipo "immutabile" non dovrebbe avere un parametro di un tipo di interfaccia perché le interfacce C # non possono specificare il contratto di immutabilità. Pertanto, la linea guida che stai proponendo non ha senso in C # perché non puoi farlo in primo luogo (e le versioni future delle lingue sono improbabili che ti consentano di farlo).

La tua domanda deriva da un sottile fraintendimento degli articoli di Eric Lippert sull'immutabilità. Eric non ha definito le interfacce IStack<T> e IQueue<T> per specificare i contratti per stack e code immutabili. Non lo fanno. Li ha definiti per comodità. Queste interfacce gli hanno permesso di definire diversi tipi di stack e code vuoti. Possiamo proporre una diversa progettazione e implementazione di uno stack immutabile che utilizza un singolo tipo senza richiedere un'interfaccia o un tipo separato per rappresentare lo stack vuoto, ma il codice risultante non sarà così pulito e sarebbe un po 'meno efficiente.

Ora atteniamoci al design di Eric. Un metodo che richiede uno stack immutabile deve avere il parametro di tipo Stack<T> piuttosto che l'interfaccia generale IStack<T> che rappresenta il tipo di dati astratto di uno stack in senso generale. Non è ovvio come farlo quando si usa lo stack immutabile di Eric e lui non ha discusso di questo nei suoi articoli, ma è possibile. Il problema è con il tipo di pila vuota. Puoi risolvere questo problema assicurandoti di non ottenere mai una pila vuota. Questo può essere assicurato spingendo un valore fittizio come primo valore sullo stack e mai saltandolo. In questo modo, puoi tranquillamente trasmettere i risultati di Push e Pop a Stack<T> .

Avere Stack<T> implementa IStack<T> può essere utile. È possibile definire metodi che richiedono uno stack, qualsiasi stack, non necessariamente uno stack immutabile. Questi metodi possono avere un parametro di tipo IStack<T> . Ciò ti consente di passare pile immutabili su di esso. Idealmente, IStack<T> sarebbe parte della libreria standard stessa. In .NET, non c'è IStack<T> , ma ci sono altre interfacce standard che lo stack immutabile può implementare, rendendo il tipo più utile.

La progettazione alternativa e l'implementazione dello stack immutabile cui ho fatto riferimento in precedenza utilizza un'interfaccia chiamata IImmutableStack<T> . Ovviamente, inserire "Immutable" nel nome dell'interfaccia non rende immutabile ogni tipo che lo implementa. Tuttavia, in questa interfaccia, il contratto di immutabilità è solo verbale. Un buon sviluppatore dovrebbe rispettarlo.

Se stai sviluppando una piccola libreria interna, puoi essere d'accordo con tutti i membri del team per onorare questo contratto e puoi usare IImmutableStack<T> come tipo di parametri. Altrimenti, non dovresti usare il tipo di interfaccia.

Vorrei aggiungere, dal momento che hai codificato la domanda C #, che nella specifica C #, non esistono DTO e POD. Pertanto, scartarli o definirli con precisione migliora la domanda.

    
risposta data 22.10.2016 - 02:58
fonte

Leggi altre domande sui tag