Sollte die Konsistenz der Programmkonvention vorgezogen werden?

14

Sollte beim Entwerfen einer Klasse eine konsistente Verhaltensweise gegenüber der üblichen Programmierpraxis bevorzugt werden? Um ein konkretes Beispiel zu geben:

Eine übliche Konvention lautet: Wenn eine Klasse ein Objekt besitzt (z. B. es erstellt hat), ist sie dafür verantwortlich, es zu bereinigen, sobald es fertig ist. Ein spezielles Beispiel wäre in .NET, dass Ihre Klasse, wenn sie ein IDisposableObjekt besitzt, es am Ende seiner Lebensdauer entsorgen sollte. Und wenn Sie es nicht besitzen, berühren Sie es nicht.

Wenn wir uns nun die StreamWriterKlasse in .NET ansehen, können wir in der Dokumentation feststellen, dass sie den zugrunde liegenden Stream schließt, wenn er geschlossen / entsorgt wird. Dies ist in Fällen erforderlich, in denen StreamWriterdurch die Übergabe eines Dateinamens als Writer der zugrunde liegende Dateistream erstellt und daher geschlossen werden muss. Man kann aber auch einen externen Stream übergeben, den der Writer ebenfalls schließt.

Das hat mich viele Male geärgert (ja, ich weiß, dass Sie einen nicht schließenden Wrapper erstellen können, aber das ist nicht der Punkt), aber anscheinend hat Microsoft die Entscheidung getroffen, dass es konsistenter ist, den Stream immer zu schließen, egal woher er kommt.

Als ich kommen über ein solches Muster in einer meiner Klassen in der Regel ich schaffen ownsFooBarFlagge , die in den Fällen auf false gesetzt wird , wo FooBarüber den Konstruktor injiziert wird und ansonsten true. Auf diese Weise wird die Verantwortung für die Bereinigung auf den Aufrufer übertragen, wenn er die Instanz explizit übergibt.

Jetzt frage ich mich, ob vielleicht die Beständigkeit der Best Practice vorgezogen werden sollte (oder ob meine Best Practice nicht so gut ist). Irgendwelche Argumente dafür / dagegen?

Zur Verdeutlichung bearbeiten

Mit "Konsistenz" meine ich: Das konsistente Verhalten der Klasse, die immer den Besitz übernimmt (und den Stream schließt), im Gegensatz zu "Best Practice", den Besitz eines Objekts nur dann zu übernehmen, wenn Sie es erstellt oder den Besitz explizit übertragen haben.

Als Beispiel, wo es reizend ist:

Angenommen, Sie haben zwei Klassen (aus einer Bibliothek eines Drittanbieters), die einen Stream akzeptieren, um etwas damit zu tun, z. B. Daten zu erstellen und zu verarbeiten:

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

Jetzt möchte ich es so benutzen:

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

Dies schlägt mit einer Ausnahme Cannot access a closed streamim Datenprozessor fehl . Es ist ein bisschen konstruiert, sollte aber den Punkt veranschaulichen. Es gibt verschiedene Möglichkeiten, das Problem zu beheben, aber ich habe trotzdem das Gefühl, dass ich etwas umgehe, das ich nicht müsste.

ChrisWue
quelle
Würde es Ihnen etwas ausmachen, herauszufinden, WARUM das dargestellte Verhalten des StreamWriter Ihnen Schmerzen bereitet? Möchten Sie den gleichen Stream woanders nutzen? Warum?
Job
@Job: Ich habe meine Frage geklärt
ChrisWue

Antworten:

9

Ich benutze diese Technik auch, ich nenne sie "Handoff" und ich glaube, sie verdient den Status eines Musters. Wenn ein Objekt A ein verfügbares Objekt B als Konstruktionszeitparameter akzeptiert, akzeptiert es auch einen Booleschen Wert namens "Handoff" (der standardmäßig "false" ist). Wenn dies zutrifft, führt die Entsorgung von A zur Entsorgung von B.

Ich bin nicht dafür, die unglücklichen Entscheidungen anderer zu verbreiten, daher würde ich weder die schlechte Vorgehensweise von Microsoft als etablierte Konvention akzeptieren, noch würde ich die blinde Verfolgung der unglücklichen Entscheidungen von Microsoft durch andere als "beständiges Verhalten" in irgendeiner Weise, Form oder Form betrachten .

Mike Nakis
quelle
Hinweis: Ich habe eine formale Definition dieses Musters in einem Beitrag in meinem Blog geschrieben: michael.gr: Das "Handoff" -Muster .
Mike Nakis
Wenn als weitere Erweiterung des handOffMusters eine solche Eigenschaft im Konstruktor festgelegt ist, es jedoch eine andere Methode zum Ändern gibt, sollte diese andere Methode auch ein handOffFlag akzeptieren . Wenn handOfffür den derzeit gehaltenen Artikel angegeben wurde, sollte er entsorgt werden, der neue Artikel sollte akzeptiert und das interne handOffFlag aktualisiert werden, um den Status des neuen Artikels widerzuspiegeln. Die Methode sollte nicht erforderlich sein, die alten und neuen Referenzen auf Gleichheit zu prüfen, da alle Referenzen des ursprünglichen Anrufers aufgegeben werden sollten, wenn die ursprüngliche Referenz "übergeben" wurde.
Supercat
@supercat Ich stimme zu, aber ich habe ein Problem mit dem letzten Satz: Wenn die Übergabe für den aktuell gehaltenen Artikel zutrifft, der neu gelieferte Artikel jedoch in Bezug auf den aktuell gehaltenen Artikel gleich ist, wird der aktuell gehaltene Artikel entsorgt ist in der Tat die Entsorgung des neu gelieferten Artikels. Ich denke, dass das Nachliefern des gleichen Artikels illegal sein sollte.
Mike Nakis
Das Nachliefern des gleichen Gegenstands sollte im Allgemeinen illegal sein, es sei denn, das Objekt ist unveränderlich, enthält keine Ressourcen und es ist egal, ob es entsorgt wird. Wenn beispielsweise DisposeAnforderungen für Systempinsel ignoriert werden, kann eine "color.CreateBrush" -Methode nach Belieben entweder einen neuen Pinsel erstellen (der entsorgt werden muss) oder einen Systempinsel zurückgeben (der die Entsorgung ignoriert). Die einfachste Möglichkeit, dies zu verhindern Die Wiederverwendung eines Objekts, wenn es um die Entsorgung geht, aber die Wiederverwendung zulassen, wenn dies nicht der Fall ist, besteht darin, es zu entsorgen.
Supercat
3

Ich denke du hast recht, um ehrlich zu sein. Ich denke, dass Microsoft die StreamWriter-Klasse aus den von Ihnen beschriebenen Gründen durcheinander gebracht hat.

Allerdings habe ich seitdem eine Menge Code gesehen, in dem die Leute nicht einmal versuchen, ihren eigenen Stream zu entsorgen, weil das Framework dies für sie erledigt. Das Reparieren wird also mehr schaden als nützen.

pdr
quelle
2

Ich würde mit Konsequenz gehen. Wie Sie den meisten Leuten gesagt haben, ist es sinnvoll, dass Ihr Objekt, wenn es ein untergeordnetes Objekt erstellt, es besitzt und zerstört. Wenn es von außen referenziert wird, hält zu einem bestimmten Zeitpunkt auch "das Äußere" das gleiche Objekt und es sollte nicht im Besitz sein. Wenn Sie das Eigentum von außen auf Ihr Objekt übertragen möchten, verwenden Sie die Attach / Detach-Methoden oder einen Konstruktor, der einen Booleschen Wert akzeptiert, der die Übertragung des Eigentums verdeutlicht.

Nachdem ich all das zur vollständigen Beantwortung eingegeben habe, denke ich, dass die meisten generischen Entwurfsmuster nicht unbedingt in dieselbe Kategorie fallen wie die StreamWriter / StreamReader-Klassen. Ich habe immer gedacht, dass der Grund, warum sich MS für die automatische Übernahme des Eigentums durch StreamWriter / Reader entschieden hat, darin besteht, dass es in diesem speziellen Fall wenig sinnvoll ist, das gemeinsame Eigentum zu haben. Es ist nicht möglich, dass zwei verschiedene Objekte gleichzeitig aus demselben Stream gelesen / in denselben Stream geschrieben werden. Ich bin sicher, dass Sie eine Art deterministischen Zeitanteil schreiben könnten, aber das wäre eine Hölle eines Anti-Musters. Das ist wahrscheinlich der Grund, warum sie sagten, da es keinen Sinn macht, einen Stream zu teilen, wollen wir ihn einfach immer übernehmen. Aber ich würde dieses Verhalten niemals als allgemeine Konvention für alle Klassen betrachten.

Sie könnten Streams als ein konsistentes Muster betrachten, bei dem das Objekt, das übergeben wird, aus Entwurfssicht nicht gemeinsam genutzt werden kann. Ohne zusätzliche Flags übernimmt der Reader / Writer lediglich sein Eigentum.

DXM
quelle
Ich habe meine Frage geklärt - mit Konsequenz meinte ich das Verhalten, immer die Verantwortung zu übernehmen.
ChrisWue
2

Achtung. Was Sie als "Konsistenz" bezeichnen, könnte eine zufällige Ansammlung von Gewohnheiten sein.

"Benutzeroberflächen auf Konsistenz abstimmen", SIGCHI Bulletin 20, (1989), 63-65. Entschuldigung, kein Link, es ist ein alter Artikel. "... ein zweitägiger Workshop von 15 Experten konnte keine Definition von Konsistenz erstellen." Ja, es geht um "Schnittstelle", aber ich glaube, wenn Sie eine Weile über "Konsistenz" nachdenken, werden Sie feststellen, dass Sie es auch nicht definieren können.

Bruce Ediger
quelle
Sie haben Recht, wenn die Experten aus verschiedenen Kreisen stammen, aber bei der Entwicklung von Code fiel es mir leicht, einem bestehenden Muster zu folgen (oder einer Gewohnheit, wenn Sie so wollen), mit der mein Team bereits vertraut ist. Neulich erkundigte sich einer unserer Kollegen nach einigen asynchronen Vorgängen und als ich ihm sagte, dass sie sich genauso verhalten wie Win32 Overlapped I / O, verstand er in 30 Sekunden die gesamte Oberfläche ... in diesem Fall beides Ich und er kamen aus dem gleichen Server-Back-End-Win32-Hintergrund. Konsistenz ftw! :)
DXM
@ Bruce, ich verstehe, ich dachte, es sei klar, was ich konsequent meinte, aber anscheinend war es nicht so.
ChrisWue