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 IDisposable
Objekt besitzt, es am Ende seiner Lebensdauer entsorgen sollte. Und wenn Sie es nicht besitzen, berühren Sie es nicht.
Wenn wir uns nun die StreamWriter
Klasse 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 StreamWriter
durch 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 ownsFooBar
Flagge , 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 stream
im 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.
Antworten:
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 .
quelle
handOff
Musters eine solche Eigenschaft im Konstruktor festgelegt ist, es jedoch eine andere Methode zum Ändern gibt, sollte diese andere Methode auch einhandOff
Flag akzeptieren . WennhandOff
für den derzeit gehaltenen Artikel angegeben wurde, sollte er entsorgt werden, der neue Artikel sollte akzeptiert und das internehandOff
Flag 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.Dispose
Anforderungen 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.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.
quelle
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.
quelle
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.
quelle