Gute oder schlechte Praxis? Objekte im Getter initialisieren

167

Ich habe eine seltsame Angewohnheit, wie es scheint ... zumindest laut meinem Kollegen. Wir haben zusammen an einem kleinen Projekt gearbeitet. Ich habe die Klassen so geschrieben (vereinfachtes Beispiel):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

Im Grunde genommen initialisiere ich ein Feld nur, wenn ein Getter aufgerufen wird und das Feld immer noch null ist. Ich dachte, dies würde die Überlastung verringern, indem keine Eigenschaften initialisiert werden, die nirgendwo verwendet werden.

ETA: Der Grund, warum ich dies getan habe, ist, dass meine Klasse mehrere Eigenschaften hat, die eine Instanz einer anderen Klasse zurückgeben, die wiederum auch Eigenschaften mit noch mehr Klassen haben, und so weiter. Wenn Sie den Konstruktor für die oberste Klasse aufrufen, werden anschließend alle Konstruktoren für alle diese Klassen aufgerufen, wenn nicht immer alle benötigt werden.

Gibt es andere Einwände gegen diese Praxis als persönliche Vorlieben?

UPDATE: Ich habe die vielen unterschiedlichen Meinungen in Bezug auf diese Frage berücksichtigt und werde zu meiner akzeptierten Antwort stehen. Jetzt habe ich das Konzept jedoch viel besser verstanden und kann entscheiden, wann ich es verwenden soll und wann nicht.

Nachteile:

  • Thread-Sicherheitsprobleme
  • Eine "Setter" -Anforderung nicht befolgen, wenn der übergebene Wert null ist
  • Mikrooptimierungen
  • Die Ausnahmebehandlung sollte in einem Konstruktor erfolgen
  • Im Klassencode muss nach Null gesucht werden

Vorteile:

  • Mikrooptimierungen
  • Eigenschaften geben niemals null zurück
  • Verzögern oder vermeiden Sie das Laden "schwerer" Objekte

Die meisten Nachteile gelten nicht für meine aktuelle Bibliothek, ich müsste jedoch testen, ob die "Mikrooptimierungen" überhaupt etwas optimieren.

LETZTES UPDATE:

Okay, ich habe meine Antwort geändert. Meine ursprüngliche Frage war, ob dies eine gute Angewohnheit ist oder nicht. Und ich bin jetzt davon überzeugt, dass es nicht so ist. Vielleicht werde ich es in einigen Teilen meines aktuellen Codes noch verwenden, aber nicht unbedingt und definitiv nicht die ganze Zeit. Also werde ich meine Gewohnheit verlieren und darüber nachdenken, bevor ich es benutze. Vielen Dank an alle!

John Willemse
quelle
14
Dies ist das Lazy-Load-Muster, das Ihnen hier nicht gerade einen wunderbaren Vorteil verschafft, aber es ist imho immer noch eine gute Sache.
Machinarius
28
Eine verzögerte Instanziierung ist sinnvoll, wenn Sie messbare Auswirkungen auf die Leistung haben oder wenn diese Mitglieder selten verwendet werden und übermäßig viel Speicher verbrauchen oder wenn die Instanziierung lange dauert und nur bei Bedarf erfolgen soll. Berücksichtigen Sie auf jeden Fall Thread-Sicherheitsprobleme (Ihr aktueller Code ist dies nicht ) und erwägen Sie die Verwendung der bereitgestellten Lazy <T> -Klasse.
Chris Sinclair
10
Ich denke, diese Frage passt besser auf codereview.stackexchange.com
Eduardo Brites
7
@PLB es ist kein Singleton-Muster.
Colin Mackay
30
Ich bin überrascht, dass niemand einen schwerwiegenden Fehler mit diesem Code erwähnt hat. Sie haben ein öffentliches Eigentum, das ich von außen einstellen kann. Wenn ich es auf NULL setze, erstellen Sie immer ein neues Objekt und ignorieren meinen Setter-Zugriff. Dies könnte ein sehr schwerwiegender Fehler sein. Für private Immobilien könnte dies okie sein. Persönlich mag ich solche vorzeitigen Optimierungen nicht. Fügt Komplexität ohne zusätzlichen Nutzen hinzu.
SolutionYogi

Antworten:

170

Was Sie hier haben, ist eine - naive - Implementierung der "faulen Initialisierung".

Kurze Antwort:

Die bedingungslose Verwendung der verzögerten Initialisierung ist keine gute Idee. Es hat seine Plätze, aber man muss die Auswirkungen dieser Lösung berücksichtigen.

Hintergrund und Erklärung:

Konkrete Implementierung:
Schauen wir uns zunächst Ihr konkretes Beispiel an und warum ich die Implementierung für naiv halte:

  1. Es verstößt gegen das Prinzip der geringsten Überraschung (POLS) . Wenn einer Eigenschaft ein Wert zugewiesen wird, wird erwartet, dass dieser Wert zurückgegeben wird. In Ihrer Implementierung ist dies nicht der Fall für null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
    
  2. Es führt zu einigen Threading-Problemen: Zwei Aufrufer von foo.Barverschiedenen Threads können möglicherweise zwei verschiedene Instanzen von erhalten, Barund einer von ihnen hat keine Verbindung zur FooInstanz. Alle an dieser BarInstanz vorgenommenen Änderungen gehen stillschweigend verloren.
    Dies ist ein weiterer Fall eines Verstoßes gegen POLS. Wenn nur auf den gespeicherten Wert einer Eigenschaft zugegriffen wird, wird erwartet, dass sie threadsicher ist. Während Sie argumentieren könnten, dass die Klasse einfach nicht threadsicher ist - einschließlich des Getters Ihres Eigentums -, müssten Sie dies ordnungsgemäß dokumentieren, da dies nicht der Normalfall ist. Darüber hinaus ist die Einführung dieses Themas nicht erforderlich, wie wir in Kürze sehen werden.

Im Allgemeinen:
Es ist jetzt an der Zeit, die verzögerte Initialisierung im Allgemeinen zu betrachten: Die
verzögerte Initialisierung wird normalerweise verwendet, um die Erstellung von Objekten zu verzögern, deren Erstellung lange dauert oder die nach ihrer vollständigen Erstellung viel Speicher benötigen.
Dies ist ein sehr wichtiger Grund für die Verwendung der verzögerten Initialisierung.

Solche Eigenschaften haben jedoch normalerweise keine Setter, wodurch das oben erwähnte erste Problem beseitigt wird.
Darüber hinaus würde eine thread-sichere Implementierung verwendet Lazy<T>, um das zweite Problem zu vermeiden.

Selbst wenn diese beiden Punkte bei der Implementierung einer Lazy-Eigenschaft berücksichtigt werden, sind die folgenden Punkte allgemeine Probleme dieses Musters:

  1. Die Konstruktion des Objekts kann nicht erfolgreich sein, was zu einer Ausnahme von einem Property Getter führt. Dies ist eine weitere Verletzung von POLS und sollte daher vermieden werden. Selbst der Abschnitt über Eigenschaften in den "Entwurfsrichtlinien für die Entwicklung von Klassenbibliotheken" besagt ausdrücklich, dass Eigenschafts-Getter keine Ausnahmen auslösen sollten:

    Vermeiden Sie es, Ausnahmen von Immobilien-Gettern zu werfen.

    Property Getter sollten einfache Operationen ohne Vorbedingungen sein. Wenn ein Getter eine Ausnahme auslösen könnte, sollten Sie die Eigenschaft als Methode neu gestalten.

  2. Automatische Optimierungen durch den Compiler sind beeinträchtigt, nämlich Inlining und Verzweigungsvorhersage. Eine ausführliche Erklärung finden Sie in der Antwort von Bill K.

Die Schlussfolgerung dieser Punkte lautet wie folgt:
Für jede einzelne Eigenschaft, die träge implementiert wird, sollten Sie diese Punkte berücksichtigt haben.
Dies bedeutet, dass es sich um eine Einzelfallentscheidung handelt, die nicht als allgemeine Best Practice angesehen werden kann.

Dieses Muster hat seinen Platz, ist jedoch keine allgemeine Best Practice bei der Implementierung von Klassen. Es sollte aus den oben genannten Gründen nicht unbedingt verwendet werden .


In diesem Abschnitt möchte ich einige der Punkte diskutieren, die andere als Argumente für die bedingungslose Verwendung der verzögerten Initialisierung vorgebracht haben:

  1. Serialisierung:
    EricJ gibt in einem Kommentar an:

    Bei einem Objekt, das möglicherweise serialisiert wird, wird der Konstruktor nicht aufgerufen, wenn es deserialisiert wird (abhängig vom Serializer, aber viele gängige verhalten sich so). Wenn Sie den Initialisierungscode in den Konstruktor einfügen, müssen Sie zusätzliche Unterstützung für die Deserialisierung bereitstellen. Dieses Muster vermeidet diese spezielle Codierung.

    Es gibt mehrere Probleme mit diesem Argument:

    1. Die meisten Objekte werden niemals serialisiert. Das Hinzufügen einer Unterstützung, wenn diese nicht benötigt wird, verstößt gegen YAGNI .
    2. Wenn eine Klasse die Serialisierung unterstützen muss, gibt es Möglichkeiten, sie ohne eine Problemumgehung zu aktivieren, die auf den ersten Blick nichts mit der Serialisierung zu tun hat.
  2. Mikrooptimierung: Ihr Hauptargument ist, dass Sie die Objekte nur dann erstellen möchten, wenn tatsächlich jemand darauf zugreift. Sie sprechen also tatsächlich über die Optimierung der Speichernutzung.
    Ich stimme diesem Argument aus folgenden Gründen nicht zu:

    1. In den meisten Fällen haben einige weitere Objekte im Speicher keinerlei Auswirkungen auf irgendetwas. Moderne Computer haben viel Speicherplatz. Ohne einen Fall von tatsächlichen Problemen, die von einem Profiler bestätigt wurden, handelt es sich um eine vorzeitige Optimierung, und es gibt gute Gründe dagegen.
    2. Ich erkenne die Tatsache an, dass diese Art der Optimierung manchmal gerechtfertigt ist. Aber selbst in diesen Fällen scheint eine verzögerte Initialisierung nicht die richtige Lösung zu sein. Es gibt zwei Gründe, die dagegen sprechen:

      1. Eine verzögerte Initialisierung beeinträchtigt möglicherweise die Leistung. Vielleicht nur am Rande, aber wie Bills Antwort zeigte, ist die Wirkung größer, als man auf den ersten Blick denken könnte. Dieser Ansatz tauscht also im Wesentlichen Leistung gegen Speicher.
      2. Wenn Sie ein Design haben, bei dem es üblich ist, nur Teile der Klasse zu verwenden, deutet dies auf ein Problem mit dem Design selbst hin: Die betreffende Klasse hat höchstwahrscheinlich mehr als eine Verantwortung. Die Lösung wäre, die Klasse in mehrere fokussiertere Klassen aufzuteilen.
Daniel Hilgarth
quelle
4
@ JohnWillemse: Das ist ein Problem Ihrer Architektur. Sie sollten Ihre Klassen so umgestalten, dass sie kleiner und fokussierter sind. Erstellen Sie keine Klasse für 5 verschiedene Dinge / Aufgaben. Erstellen Sie stattdessen 5 Klassen.
Daniel Hilgarth
26
@ JohnWillemse Vielleicht ist dies dann ein Fall vorzeitiger Optimierung. Sofern Sie keinen gemessenen Leistungs- / Speicherengpass haben, würde ich davon abraten, da dies die Komplexität erhöht und Threading-Probleme mit sich bringt.
Chris Sinclair
2
+1, dies ist keine gute Wahl für 95% der Klassen. Die verzögerte Initialisierung hat ihre Vorteile, sollte jedoch nicht für ALLE Eigenschaften verallgemeinert werden. Es erhöht die Komplexität, die Schwierigkeit, den Code zu lesen, Probleme mit der Thread-Sicherheit ... in 99% der Fälle ist keine Optimierung erkennbar. Wie SolutionYogi als Kommentar sagte, ist der Code des OP fehlerhaft, was beweist, dass dieses Muster nicht trivial zu implementieren ist und vermieden werden sollte, es sei denn, eine verzögerte Initialisierung ist tatsächlich erforderlich.
Ken2k
2
@DanielHilgarth Danke, dass Sie den ganzen Weg unternommen haben, um (fast) alles aufzuschreiben, was falsch ist, wenn Sie dieses Muster bedingungslos verwenden. Gut gemacht!
Alex
1
@ DanielHilgarth Na ja und nein. Die Verletzung ist das Problem hier also ja. Aber auch 'nein', denn POLS ist genau das Prinzip, dass Sie vom Code wahrscheinlich nicht überrascht werden . Wenn Foo nicht außerhalb Ihres Programms verfügbar war, können Sie ein Risiko eingehen oder nicht. In diesem Fall kann ich fast garantieren, dass Sie irgendwann überrascht werden , da Sie nicht kontrollieren, wie auf die Eigenschaften zugegriffen wird. Das Risiko wurde einfach zu einem Fehler, und Ihre Argumentation über den nullFall wurde viel stärker. :-)
atlaste
49

Es ist eine gute Wahl für das Design. Sehr empfehlenswert für Bibliothekscode oder Kernklassen.

Es wird durch eine "verzögerte Initialisierung" oder "verzögerte Initialisierung" bezeichnet und wird im Allgemeinen von allen als eine gute Wahl für das Design angesehen.

Wenn Sie in der Deklaration von Variablen oder Konstruktoren auf Klassenebene initialisieren, haben Sie beim Erstellen Ihres Objekts zunächst den Aufwand, eine Ressource zu erstellen, die möglicherweise nie verwendet wird.

Zweitens wird die Ressource nur bei Bedarf erstellt.

Drittens vermeiden Sie Müll, der ein Objekt sammelt, das nicht verwendet wurde.

Schließlich ist es einfacher, Initialisierungsausnahmen zu behandeln, die in der Eigenschaft auftreten können, als Ausnahmen, die während der Initialisierung von Variablen auf Klassenebene oder des Konstruktors auftreten.

Es gibt Ausnahmen von dieser Regel.

Das Leistungsargument der zusätzlichen Prüfung für die Initialisierung in der Eigenschaft "get" ist unbedeutend. Das Initialisieren und Entsorgen eines Objekts ist ein bedeutenderer Leistungseinbruch als eine einfache Nullzeigerprüfung mit einem Sprung.

Entwurfsrichtlinien für die Entwicklung von Klassenbibliotheken unter http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx

Hinsichtlich Lazy<T>

Die generische Lazy<T>Klasse wurde genau für die Anforderungen des Posters erstellt (siehe Lazy Initialization unter http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx) . Wenn Sie ältere Versionen von .NET haben, müssen Sie das in der Frage dargestellte Codemuster verwenden. Dieses Codemuster ist so verbreitet geworden, dass Microsoft es für angebracht hielt, eine Klasse in die neuesten .NET-Bibliotheken aufzunehmen, um die Implementierung des Musters zu vereinfachen. Wenn Ihre Implementierung Thread-Sicherheit benötigt, müssen Sie diese hinzufügen.

Primitive Datentypen und einfache Klassen

Offensichtlich werden Sie die Lazy-Initialisierung nicht für primitive Datentypen oder einfache Klassen verwenden List<string>.

Bevor ich über Lazy kommentiere

Lazy<T> wurde in .NET 4.0 eingeführt. Fügen Sie daher bitte keinen weiteren Kommentar zu dieser Klasse hinzu.

Vor dem Kommentieren von Mikrooptimierungen

Wenn Sie Bibliotheken erstellen, müssen Sie alle Optimierungen berücksichtigen. In den .NET-Klassen sehen Sie beispielsweise Bit-Arrays, die für boolesche Klassenvariablen im gesamten Code verwendet werden, um den Speicherverbrauch und die Speicherfragmentierung zu reduzieren, um nur zwei "Mikrooptimierungen" zu nennen.

In Bezug auf Benutzeroberflächen

Sie werden die verzögerte Initialisierung nicht für Klassen verwenden, die direkt von der Benutzeroberfläche verwendet werden. Letzte Woche habe ich den größten Teil eines Tages damit verbracht, das langsame Laden von acht Sammlungen zu entfernen, die in einem Ansichtsmodell für Kombinationsfelder verwendet wurden. Ich habe eine LookupManager, die das verzögerte Laden und Zwischenspeichern von Sammlungen übernimmt, die von jedem Benutzeroberflächenelement benötigt werden.

"Setter"

Ich habe noch nie eine Set-Eigenschaft ("Setter") für eine faul geladene Eigenschaft verwendet. Daher würden Sie niemals zulassen foo.Bar = null;. Wenn Sie festlegen müssen, Barwürde ich eine Methode namens aufgerufen SetBar(Bar value)und keine Lazy-Initialisierung verwenden

Sammlungen

Klassenerfassungseigenschaften werden bei der Deklaration immer initialisiert, da sie niemals null sein sollten.

Komplexe Klassen

Lassen Sie mich das anders wiederholen, Sie verwenden die Lazy-Initialisierung für komplexe Klassen. Welches sind in der Regel schlecht gestaltete Klassen.

zuletzt

Ich habe nie gesagt, dies für alle Klassen oder in allen Fällen zu tun. Es ist eine schlechte Angewohnheit.

AMissico
quelle
6
Wenn Sie foo.Bar mehrmals in verschiedenen Threads aufrufen können, ohne dazwischen liegende Werteinstellungen vorzunehmen, aber unterschiedliche Werte erhalten, haben Sie eine miese schlechte Klasse.
Lie Ryan
25
Ich denke, dies ist eine schlechte Faustregel ohne viele Überlegungen. Sofern Bar kein bekanntes Ressourcenfresser ist, ist dies eine unnötige Mikrooptimierung. Und für den Fall, dass Bar ressourcenintensiv ist, ist der thread-sichere Lazy <T> in .net integriert.
Andrew Hanlon
10
"Es ist einfacher, Initialisierungsausnahmen zu behandeln, die in der Eigenschaft auftreten können, als Ausnahmen, die während der Initialisierung von Variablen auf Klassenebene oder des Konstruktors auftreten." - Okay, das ist albern. Wenn ein Objekt aus irgendeinem Grund nicht initialisiert werden kann, möchte ich es so schnell wie möglich wissen. Dh sofort, wenn es gebaut ist. Es gibt gute Argumente für die Verwendung faul Initialisierung gemacht werden , aber ich glaube nicht , es ist eine gute Idee , es zu benutzen pervasively .
Millimoose
20
Ich bin wirklich besorgt, dass andere Entwickler diese Antwort sehen und denken, dass dies in der Tat eine gute Praxis ist (oh Junge). Dies ist eine sehr, sehr schlechte Praxis, wenn Sie es bedingungslos verwenden. Abgesehen von dem, was bereits gesagt wurde, machen Sie das Leben aller so viel schwieriger (für Kundenentwickler und Wartungsentwickler), und zwar für so wenig Gewinn (wenn überhaupt). Sie sollten von den Profis hören: Donald Knuth sagte in der Serie The Art of Computer Programming: "Vorzeitige Optimierung ist die Wurzel allen Übels." Was Sie tun, ist nicht nur böse, sondern auch teuflisch!
Alex
4
Es gibt so viele Indikatoren, dass Sie die falsche Antwort (und die falsche Programmierentscheidung) wählen. Sie haben mehr Nachteile als Vorteile in Ihrer Liste. Sie haben mehr Leute, die dafür bürgen als dafür. Die erfahreneren Mitglieder dieser Site (@BillK und @DanielHilgarth), die in dieser Frage gepostet haben, sind dagegen. Ihr Mitarbeiter hat Ihnen bereits gesagt, dass es falsch ist. Im Ernst, es ist falsch! Wenn ich einen der Entwickler meines Teams (ich bin ein Teamleiter) dabei erwische, nimmt er sich eine 5-minütige Auszeit und wird dann belehrt, warum er dies niemals tun sollte.
Alex
17

Denken Sie darüber nach, ein solches Muster mit zu implementieren Lazy<T>?

Zusätzlich zur einfachen Erstellung von verzögert geladenen Objekten erhalten Sie Thread-Sicherheit, während das Objekt initialisiert wird:

Wie andere sagten, laden Sie Objekte träge, wenn sie wirklich ressourcenintensiv sind oder es einige Zeit dauert, sie während der Objektkonstruktionszeit zu laden.

Matías Fidemraizer
quelle
Danke, ich verstehe es jetzt und werde es jetzt definitiv untersuchen Lazy<T>und es unterlassen, die Art und Weise zu verwenden, wie ich es immer getan habe.
John Willemse
1
Du bekommst keine magische Fadensicherheit ... du musst immer noch darüber nachdenken. Von MSDN:Making the Lazy<T> object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
Eric J.
@EricJ. Natürlich, natürlich. Sie erhalten nur Thread-Sicherheit, wenn Sie das Objekt initialisieren. Später müssen Sie sich jedoch wie jedes andere Objekt mit der Synchronisierung befassen.
Matías Fidemraizer
9

Ich denke, es hängt davon ab, was Sie initialisieren. Ich würde es wahrscheinlich nicht für eine Liste tun, da die Baukosten ziemlich gering sind, so dass es in den Konstruktor gehen kann. Aber wenn es eine vorab ausgefüllte Liste wäre, würde ich es wahrscheinlich nicht tun, bis sie zum ersten Mal benötigt würde.

Wenn die Baukosten die Kosten für eine bedingte Überprüfung jedes Zugriffs überwiegen, erstellen Sie sie grundsätzlich faul. Wenn nicht, machen Sie es im Konstruktor.

Colin Mackay
quelle
Danke dir! Das macht Sinn.
John Willemse
9

Der Nachteil, den ich sehen kann, ist, dass wenn Sie fragen möchten, ob Bars null ist, dies niemals der Fall sein würde und Sie die Liste dort erstellen würden.

Luis Tellez
quelle
Ich denke nicht, dass das ein Nachteil ist.
Peter Porfy
Warum ist das ein Nachteil? Prüfen Sie stattdessen mit any gegen null. if (! Foo.Bars.Any ())
s.meijer
6
@ PeterPorfy: Es verletzt POLS . Sie setzen nullein, bekommen es aber nicht zurück. Normalerweise nehmen Sie an, dass Sie denselben Wert zurückerhalten, den Sie in eine Eigenschaft eingegeben haben.
Daniel Hilgarth
@ DanielHilgarth Nochmals vielen Dank. Das ist ein sehr berechtigtes Argument, über das ich vorher nicht nachgedacht hatte.
John Willemse
6
@AMissico: Es ist kein erfundenes Konzept. Ähnlich wie beim Drücken eines Knopfes neben einer Vordertür erwartet wird, dass eine Türklingel klingelt, wird erwartet, dass sich Dinge, die wie Eigenschaften aussehen, wie Eigenschaften verhalten. Das Öffnen einer Falltür unter Ihren Füßen ist ein überraschendes Verhalten, insbesondere wenn der Knopf nicht als solcher gekennzeichnet ist.
Bryan Boettcher
8

Eine verzögerte Instanziierung / Initialisierung ist ein perfektes Muster. Beachten Sie jedoch, dass die Verbraucher Ihrer API in der Regel nicht erwarten, dass Getter und Setter vom POV des Endbenutzers erkennbare Zeit in Anspruch nehmen (oder fehlschlagen).

Tormod
quelle
1
Ich stimme zu und habe meine Frage ein wenig bearbeitet. Ich erwarte, dass eine vollständige Kette zugrunde liegender Konstruktoren mehr Zeit benötigt, als Klassen nur dann zu instanziieren, wenn sie benötigt werden.
John Willemse
8

Ich wollte gerade Daniels Antwort kommentieren, aber ich glaube ehrlich gesagt nicht, dass es weit genug geht.

Obwohl dies ein sehr gutes Muster ist, das in bestimmten Situationen verwendet werden kann (z. B. wenn das Objekt aus der Datenbank initialisiert wird), ist es eine SCHRECKLICHE Angewohnheit, sich darauf einzulassen.

Eines der besten Dinge an einem Objekt ist, dass es eine sichere, vertrauenswürdige Umgebung bietet. Der beste Fall ist, wenn Sie so viele Felder wie möglich "Final" machen und sie alle mit dem Konstruktor ausfüllen. Dies macht Ihre Klasse ziemlich kugelsicher. Das Ändern von Feldern durch Setter ist etwas weniger, aber nicht schrecklich. Zum Beispiel:

Klasse SafeClass
{
    String name = "";
    Ganzzahliges Alter = 0;

    public void setName (String newName)
    {
        assert (newName! = null)
        name = newName;
    } // folge diesem Muster für das Alter
    ...
    public String toString () {
        String s = "Sichere Klasse hat Name:" + Name + "und Alter:" + Alter
    }}
}}

Mit Ihrem Muster würde die toString-Methode folgendermaßen aussehen:

    if (name == null)
        neue IllegalStateException auslösen ("SafeClass ist in einen illegalen Zustand geraten! Name ist null")
    if (age == null)
        neue IllegalStateException auslösen ("SafeClass ist in einen illegalen Zustand geraten! Alter ist null")

    public String toString () {
        String s = "Sichere Klasse hat Name:" + Name + "und Alter:" + Alter
    }}

Darüber hinaus benötigen Sie überall dort, wo Sie möglicherweise dieses Objekt in Ihrer Klasse verwenden, Nullprüfungen. (Außerhalb Ihrer Klasse ist dies aufgrund der Nullprüfung im Getter sicher, aber Sie sollten hauptsächlich Ihre Klassenmitglieder innerhalb der Klasse verwenden.)

Auch Ihre Klasse befindet sich ständig in einem unsicheren Zustand. Wenn Sie beispielsweise beschlossen haben, diese Klasse durch Hinzufügen einiger Anmerkungen zu einer Klasse im Ruhezustand zu machen, wie würden Sie dies tun?

Wenn Sie eine Entscheidung treffen, die auf einer Mikrooptomisierung ohne Anforderungen und Tests basiert, ist dies mit ziemlicher Sicherheit die falsche Entscheidung. Tatsächlich besteht eine sehr gute Chance, dass Ihr Muster das System selbst unter den idealsten Umständen tatsächlich verlangsamt, da die if-Anweisung einen Verzweigungsvorhersagefehler auf der CPU verursachen kann, der die Dinge viele, viele Male langsamer verlangsamt als Zuweisen eines Werts im Konstruktor, es sei denn, das von Ihnen erstellte Objekt ist ziemlich komplex oder stammt aus einer entfernten Datenquelle.

Ein Beispiel für das Brance-Vorhersageproblem (das wiederholt und nicht nur einmal auftritt) finden Sie in der ersten Antwort auf diese großartige Frage: Warum ist es schneller, ein sortiertes Array zu verarbeiten als ein unsortiertes Array?

Bill K.
quelle
Danke für deinen Beitrag. In meinem Fall verfügt keine der Klassen über Methoden, die möglicherweise auf Null prüfen müssen. Dies ist also kein Problem. Ich werde Ihre anderen Einwände berücksichtigen.
John Willemse
Das verstehe ich nicht wirklich. Dies bedeutet, dass Sie Ihre Mitglieder nicht in den Klassen verwenden, in denen sie gespeichert sind. Sie verwenden die Klassen lediglich als Datenstrukturen. Wenn dies der Fall ist, lesen Sie möglicherweise javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html, das eine gute Beschreibung enthält, wie Ihr Code verbessert werden kann, indem Sie vermeiden, den Objektstatus extern zu manipulieren. Wenn Sie sie intern manipulieren, wie tun Sie dies, ohne alles wiederholt auf Null zu überprüfen?
Bill K
Teile dieser Antwort sind gut, aber Teile scheinen erfunden zu sein. Normalerweise würde bei Verwendung dieses Musters toString()aufgerufen getName(), nicht namedirekt verwendet.
Izkata
@ BillK Ja, die Klassen sind eine riesige Datenstruktur. Alle Arbeiten werden in statischen Klassen ausgeführt. Ich werde den Artikel unter dem Link lesen. Vielen Dank!
John Willemse
1
@izkata Tatsächlich scheint es innerhalb einer Klasse ein Problem zu sein, ob Sie einen Getter verwenden oder nicht. Die meisten Orte, an denen ich gearbeitet habe, haben das Mitglied direkt verwendet. Abgesehen davon, wenn Sie immer einen Getter verwenden, ist die if () -Methode noch schädlicher, da die Verzweigungsvorhersagefehler häufiger auftreten UND die Laufzeit aufgrund der Verzweigung wahrscheinlich mehr Probleme beim Einfügen des Getters hat. Mit Johns Offenbarung, dass es sich um Datenstrukturen und statische Klassen handelt, ist alles umstritten, das ist das, worum es mir am meisten geht.
Bill K
4

Lassen Sie mich noch einen Punkt zu vielen guten Punkten hinzufügen, die von anderen gemacht wurden ...

Der Debugger wertet ( standardmäßig ) die Eigenschaften aus, wenn er den Code durchläuft. Dies kann möglicherweise Barschneller instanziieren, als dies normalerweise durch einfaches Ausführen des Codes der Fall wäre. Mit anderen Worten, das bloße Debuggen verändert die Ausführung des Programms.

Dies kann ein Problem sein oder auch nicht (abhängig von den Nebenwirkungen), ist jedoch zu beachten.

Branko Dimitrijevic
quelle
2

Sind Sie sicher, dass Foo überhaupt etwas instanziieren sollte?

Für mich scheint es stinkend (wenn auch nicht unbedingt falsch ), Foo überhaupt etwas instanziieren zu lassen. Sofern es nicht Foos ausdrücklicher Zweck ist, eine Fabrik zu sein, sollte es nicht seine eigenen Mitarbeiter instanziieren, sondern sie in seinen Konstruktor injizieren lassen .

Wenn jedoch Foos Zweck darin besteht, Instanzen vom Typ Bar zu erstellen, sehe ich nichts Falsches daran, es faul zu machen.

KaptajnKold
quelle
4
@BenjaminGruenbaum Nein, nicht wirklich. Und respektvoll, selbst wenn es so wäre, welchen Punkt wollten Sie machen?
KaptajnKold