So schreiben Sie Konstruktoren, die ein Objekt möglicherweise nicht richtig instanziieren

11

Manchmal müssen Sie einen Konstruktor schreiben, der fehlschlagen kann. Angenommen, ich möchte ein Objekt mit einem Dateipfad instanziieren

obj = new Object("/home/user/foo_file")

Solange der Pfad auf eine entsprechende Datei verweist, ist alles in Ordnung. Aber wenn die Zeichenfolge kein gültiger Pfad ist, sollten die Dinge kaputt gehen. Aber wie?

Du könntest:

  1. eine Ausnahme auslösen
  2. Rückgabe des Nullobjekts (wenn Ihre Programmiersprache es Konstruktoren erlaubt, Werte zurückzugeben)
  3. ein gültiges Objekt zurückgeben, aber mit einem Flag, das angibt, dass sein Pfad nicht richtig gesetzt wurde (ugh)
  4. Andere?

Ich gehe davon aus, dass die "Best Practices" verschiedener Programmiersprachen dies unterschiedlich implementieren würden. Zum Beispiel denke ich, dass ObjC (2) bevorzugt. Es wäre jedoch unmöglich, (2) in C ++ zu implementieren, wo Konstruktoren void als Rückgabetyp haben müssen. In diesem Fall gehe ich davon aus, dass (1) verwendet wird.

Können Sie in der Programmiersprache Ihrer Wahl zeigen, wie Sie mit diesem Problem umgehen würden, und erklären, warum?

Garageàtrois
quelle
1
Ausnahmen in Java sind eine Möglichkeit, damit umzugehen.
Mahmoud Hossam
C ++ - Konstruktoren geben nicht zurück void- sie geben ein Objekt zurück.
Gablin
6
@gablin: Eigentlich stimmt das auch nicht ganz. newruft operator newauf, um den Speicher zuzuweisen, und dann den Konstruktor, um ihn zu füllen. Der Konstruktor gibt nichts zurück und newgibt den Zeiger zurück, von dem er erhalten hat operator new. Ob "nichts zurückgibt void" impliziert, dass "zurückgibt ", ist jedoch zu gewinnen.
Jon Purdy
@ Jon Purdy: Hm, klingt vernünftig. Guter Anruf.
Gablin

Antworten:

8

Es ist nie gut, sich auf einen Konstrukteur zu verlassen, um die Drecksarbeit zu erledigen. Außerdem ist einem anderen Programmierer auch unklar , ob im Konstruktor gearbeitet werden soll oder nicht, es sei denn, es gibt eine explizite Dokumentation, die dies angibt (und die Benutzer der Klasse haben sie gelesen oder wurden dazu aufgefordert).

Zum Beispiel (in C #):

public Sprite
{
    public Sprite(string filename)
    {
    }
}

Was passiert, wenn der Benutzer die Datei nicht sofort laden möchte? Was ist, wenn die Datei bei Bedarf zwischengespeichert werden soll? Sie können nicht. Sie könnten überlegen, ein bool loadFileArgument in den Konstruktor einzufügen, aber dies macht dies unübersichtlich, da Sie jetzt noch eine Load()Methode zum Laden der Datei benötigen .

Angesichts des aktuellen Szenarios wird es für die Benutzer der Klasse flexibler und klarer, dies zu tun:

Sprite sprite = new Sprite();
sprite.Load("mario.png");

Oder alternativ (für so etwas wie eine Ressource):

Sprite sprite = new Sprite();
sprite.Source = "mario.png";
sprite.Cache();

// On demand caching of file contents.
public void Cache()
{
     if (image == null)
     {
         try
         {
             image = new Image.FromFile(Source);
         }
         catch(...)
         {
         }
     }
}
Nick Bedford
quelle
In Ihrem Fall, wenn das Laden (..) fehlschlägt, haben wir ein völlig nutzloses Objekt. Ich bin nicht sicher, ob ich Sprite ohne Sprite will ... Aber die Frage sieht eher nach Holywar-Thema als nach echter Frage aus :)
Avtomaton
7

In Java können Sie Ausnahmen verwenden oder das Factory-Muster verwenden, mit dem Sie null zurückgeben können.

In Scala können Sie eine Option [Foo] von einer Factory-Methode zurückgeben. Das würde auch in Java funktionieren, wäre aber umständlicher.

Kim
quelle
Verwenden von Ausnahmen oder Factory auch in .NET-Sprachen.
Beth Whitezel
3

Eine Ausnahme auslösen.

Null muss überprüft werden, wenn Sie es zurückgeben können (und es wird nicht überprüft)

Dafür gibt es geprüfte Ausnahmen. Sie wissen, dass es scheitern könnte. Anrufer müssen damit umgehen.

Tim Williscroft
quelle
3

In C ++ werden Konstruktoren verwendet, um Mitglieder der Klasse zu erstellen / zu initialisieren.

Es gibt keine richtige Antwort auf diese Frage. Was ich bisher beobachtet habe, ist, dass meistens der Client (oder derjenige, der Ihre API verwenden wird) entscheidet, wie Sie mit diesen Dingen umgehen sollen.

Manchmal könnten sie Sie bitten , alle Ressourcen , das Objekt muss möglicherweise auf den Konstruktor zuweisen und werfen eine Ausnahme , wenn etwas fehlschlägt (die Erstellung des Objekts abgebrochen) oder tun nichts davon auf den Konstruktor, und stellen Sie sicher , dass die Schöpfung immer erfolgreich Überlassen Sie diese Aufgaben einigen Mitgliedsfunktionen.

Wenn Sie das Verhalten auswählen müssen, sind Ausnahmen die Standardmethode für die Behandlung von Fehlern in C ++, und Sie sollten sie verwenden, wenn Sie können.

Karlphillip
quelle
1

Sie können das Objekt auch ohne Parameter oder nur mit Parametern instanziieren, die sicher niemals ausfallen werden, und dann eine Initialisierungsfunktion oder -methode verwenden, mit der Sie sicher eine Ausnahme auslösen oder tun können, was Sie möchten.

gablin
quelle
6
Ich denke, die Rückgabe eines unbrauchbaren Objekts, das eine weitere Initialisierung erfordert, ist die schlechteste Wahl. Jetzt haben Sie ein mehrzeiliges Fragment, das bei jeder Verwendung der Klasse kopiert oder in eine neue Methode einbezogen werden muss. Sie können den Konstruktor auch die ganze Arbeit erledigen lassen und eine Ausnahme auslösen, wenn das Objekt nicht erstellt werden kann.
Kevin Cline
2
@kevin: Kommt auf das Objekt an. Fehler sind wahrscheinlich auf externe Ressourcen zurückzuführen, sodass eine Entität möglicherweise die Absicht (z. B. Dateiname) registrieren möchte, aber wiederholte Versuche zur vollständigen Initialisierung zulässt. Für ein Wertobjekt würde ich wahrscheinlich einen Fehler melden, der den zugrunde liegenden Fehler erfassen kann, also wahrscheinlich eine (umschlossene?) Ausnahme auslösen.
Dave
-4

Ich bevorzuge es, keine Klasse oder Liste im Konstruktor zu initialisieren. Initialisieren Sie eine Klasse oder Liste, wann immer Sie möchten. Z.B.

public class Test
{
    List<Employee> test = null;
}

Tu das nicht

public class Test
{
    List<Employee> test = null;

    public Test()
    {
        test = new List<Employee>();
    }
}

Initialisieren Sie stattdessen bei Bedarf, z.

public class Test
{
    List<Employee> emp = null;

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
Prüfung
quelle
1
Das ist ein schlechter Rat. Sie sollten nicht zulassen, dass sich Objekte in einem ungültigen Zustand befinden. Dafür ist der Konstruktor da. Wenn Sie eine komplexe Logik benötigen, verwenden Sie das Factory-Muster.
AlexFoxGill
1
Konstruktoren sind dazu da, die Klasseninvarianten zu ermitteln. Der Beispielcode hilft nicht, dies überhaupt zu behaupten.
Niall