La coerenza dovrebbe essere favorita rispetto alla convenzione di programmazione?


14

Quando si progetta una classe, la coerenza nel comportamento dovrebbe essere favorita rispetto alla pratica di programmazione comune? Per fare un esempio specifico:

Una convenzione comune è questa: se una classe possiede un oggetto (ad es. Lo ha creato) è responsabile di ripulirlo una volta fatto. Un esempio specifico potrebbe essere in .NET che se la tua classe possiede un IDisposableoggetto, dovrebbe eliminarlo alla fine della sua vita. E se non lo possiedi, non toccarlo.

Ora, se osserviamo la StreamWriterclasse in .NET, possiamo trovare nella documentazione che chiude il flusso sottostante quando viene chiuso / eliminato. Ciò è necessario nei casi in cui StreamWriterviene istanziato passando un nome file mentre il writer crea il flusso di file sottostante e quindi deve chiuderlo. Tuttavia si può anche passare in un flusso esterno che anche lo scrittore chiude.

Questo mi ha infastidito un sacco di volte (sì, lo so che puoi creare un wrapper senza chiusura, ma non è questo il punto), ma a quanto pare Microsoft ha preso la decisione che è più coerente chiudere sempre il flusso, non importa da dove provenga.

Quando mi imbatto in un tale schema in una delle mie classi di solito creo una ownsFooBarbandiera che viene impostata su false nei casi in cui FooBarviene iniettata tramite il costruttore e su true in caso contrario. In questo modo la responsabilità di ripulirla viene trasferita al chiamante quando passa esplicitamente l'istanza.

Ora mi chiedo se forse la coerenza dovrebbe essere a favore delle migliori pratiche (o forse la mia migliore pratica non è così buona)? Qualche argomento a favore / contro di esso?

Modifica per chiarimenti

Con "coerenza" intendo: il comportamento coerente della classe che assume sempre la proprietà (e chiude il flusso) vs "best practice" per diventare proprietario di un oggetto solo se lo hai creato o trasferito esplicitamente la proprietà.

Come per un esempio in cui sta provando:

Supponiamo di avere due classi date (da una libreria di terze parti) che accettano uno stream per fare qualcosa con esso, come la creazione e l'elaborazione di alcuni dati:

 public class DataProcessor
 {
     public Result ProcessData(Stream input)
     {
          using (var reader = new StreamReader(input))
          {
              ...
          }
     }
 }

 public class DataSource
 {
     public void GetData(Stream output)
     {
          using (var writer = new StreamWriter(output))
          {
               ....
          }
     }
 }

Ora voglio usarlo in questo modo:

 Result ProcessSomething(DataSource source)
 {
      var processor = new DataProcessor();
      ...
      var ms = new MemoryStream();
      source.GetData(ms);
      return processor.ProcessData(ms);
 }

Ciò fallirà con un'eccezione Cannot access a closed streamnel processore dei dati. È un po 'costruito ma dovrebbe illustrare il punto. Esistono vari modi per risolverlo, ma comunque sento di aggirare qualcosa che non dovrei dover fare.


Ti dispiacerebbe approfondire il PERCHÉ il comportamento illustrato di StreamWriter ti provoca dolore? Vorresti consumare lo stesso flusso altrove? Perché?
Giobbe

@Job: ho chiarito la mia domanda
ChrisWue,

Risposte:


9

Uso anche questa tecnica, la chiamo "handoff" e credo che meriti lo status di un modello. Quando un oggetto A accetta un oggetto usa e getta B come parametro del tempo di costruzione, accetta anche un valore booleano chiamato 'handoff', (che per impostazione predefinita è falso) e, se è vero, allora lo smaltimento di A viene eseguito a cascata di B.

Non sono favorevole alla proliferazione delle scelte sfortunate di altre persone, quindi non accetterei mai la cattiva pratica di Microsoft come una convenzione stabilita, né considererei il cieco seguito di altre sfortunate scelte di Microsoft come "comportamento coerente" in alcun modo, forma o forma .


Nota: ho scritto una definizione formale di questo modello in un post sul mio blog: michael.gr: il modello "Handoff" .
Mike Nakis,

Come ulteriore estensione del handOffmodello, se una tale proprietà è impostata nel costruttore, ma esiste un altro metodo per modificarlo, tale altro metodo dovrebbe accettare anche un handOffflag. Se è handOffstato specificato per l'articolo attualmente in possesso, dovrebbe essere eliminato, quindi il nuovo articolo dovrebbe essere accettato e il handOffflag interno aggiornato per riflettere lo stato del nuovo articolo. Non è necessario che il metodo controlli l'uguaglianza dei riferimenti vecchi e nuovi, poiché se il riferimento originale era "consegnato" tutti i riferimenti detenuti dal chiamante originale dovrebbero essere abbandonati.
supercat

@supercat Concordo, ma ho un problema con l'ultima frase: se il trasferimento è vero per l'articolo attualmente in possesso, ma l'articolo appena fornito è uguale in riferimento all'articolo attualmente in possesso, quindi lo smaltimento dell'articolo attualmente in possesso sta effettivamente smaltendo l'articolo appena fornito. Penso che il rifornimento dello stesso articolo dovrebbe essere illegale.
Mike Nakis,

Il rifornimento dello stesso articolo dovrebbe essere generalmente illegale, a meno che l'oggetto non sia immutabile, in realtà non contenga alcuna risorsa e non si preoccupi se viene eliminato. Ad esempio, se le Disposerichieste fossero ignorate per i pennelli di sistema, un metodo "color.CreateBrush` potrebbe a suo piacimento creare un nuovo pennello (che richiederebbe lo smaltimento) o restituire un pennello di sistema (che ignorerebbe lo smaltimento). Il modo più semplice per impedire il riutilizzo di un oggetto se si preoccupa dello smaltimento, ma consentire il riutilizzo in caso contrario, è di eliminarlo.
supercat

3

Penso che tu abbia ragione, a dire il vero. Penso che Microsoft abbia incasinato la classe StreamWriter, in particolare, per i motivi che descrivi.

Tuttavia, da allora ho visto un sacco di codice in cui le persone non provano nemmeno a smaltire il proprio Stream perché il framework lo farà per loro, quindi risolverlo ora farà più male che bene.


2

Andrei con coerenza. Come hai detto alla maggior parte delle persone, ha senso che se il tuo oggetto crea un oggetto figlio, lo possederà e lo distruggerà. Se gli viene dato un riferimento dall'esterno, ad un certo punto "l'esterno" contiene anche lo stesso oggetto e non dovrebbe essere posseduto. Se si desidera trasferire la proprietà dall'esterno nell'oggetto, utilizzare i metodi Allega / Stacca o un costruttore che accetta un valore booleano che chiarisca che la proprietà viene trasferita.

Dopo aver digitato tutto ciò per una risposta completa, penso che la maggior parte dei modelli di progettazione generici non rientrerebbe necessariamente nella stessa categoria delle classi StreamWriter / StreamReader. Ho sempre pensato che il motivo per cui MS ha scelto di far sì che StreamWriter / Reader acquisisse automaticamente la proprietà è perché in questa specifica istanza, avere la proprietà condivisa non ha molto senso. Non è possibile avere due oggetti diversi contemporaneamente letti / scritti nello stesso flusso. Sono sicuro che potresti scrivere una sorta di condivisione del tempo deterministica, ma sarebbe un inferno di un anti-schema. Quindi probabilmente è per questo che hanno detto, dal momento che non ha senso condividere un flusso, prendiamolo sempre in considerazione. Ma non prenderei in considerazione che questo comportamento sia mai diventato una convenzione generica a tutti i livelli per tutte le classi.

Potresti considerare gli Stream come un modello coerente in cui l'oggetto che viene passato non è condivisibile dal punto di vista del design e quindi senza ulteriori flag, il lettore / scrittore ne assume la proprietà.


Ho chiarito la mia domanda - con coerenza intendevo il comportamento di assumere sempre la proprietà.
ChrisWue,

2

Stai attento. Quello che pensi come "coerenza" potrebbe essere solo una raccolta casuale di abitudini.

"Coordinamento delle interfacce utente per coerenza", Bollettino SIGCHI 20, (1989), 63-65. Spiacente, nessun link, è un vecchio articolo. "... un seminario di due giorni di 15 esperti non è stato in grado di produrre una definizione di coerenza." Sì, si tratta di "interfaccia", ma credo che se pensi alla "coerenza" per un po ', scoprirai che non puoi nemmeno definirla.


hai ragione, se gli esperti si riuniscono in ambienti diversi, ma durante lo sviluppo del codice, ho trovato facile seguire alcuni schemi esistenti (o abitudine se vuoi) con cui il mio team ha già familiarità. Ad esempio, l'altro giorno uno dei nostri ragazzi stava indagando su alcune operazioni asincrone che abbiamo avuto e quando gli ho detto che si comportano allo stesso modo dell'I / O sovrapposto Win32, in 30 secondi ha capito l'intera interfaccia ... in questo caso entrambi io e lui provenivamo dallo stesso server con il back-end di sfondo Win32. Coerenza ftw! :)
DXM,

@Bruce ho capito il tuo punto - ho pensato che fosse chiaro cosa intendevo con coerenza, ma a quanto pare non lo era.
ChrisWue,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.