Frage

Wann ist es richtig für einen Konstruktor eine Ausnahme zu werfen? (Oder im Fall von Objective C: Wann ist es richtig für ein init'er Null zurück)

Es scheint mir, dass ein Konstruktor ausfallen sollte - und damit auch ablehnen, ein Objekt zu schaffen - wenn das Objekt nicht vollständig ist. Das heißt, sollte der Konstruktor haben einen Vertrag mit dem Anrufer eine der Funktions- und Arbeitsobjekt zu schaffen, auf dem Methoden sinnvoll bezeichnet werden kann? Ist das sinnvoll?

War es hilfreich?

Lösung

Die Aufgabe des Konstruktor ist das Objekt in einen nutzbaren Zustand zu bringen. Grundsätzlich gibt es zwei Denkschulen zu diesem Thema.

Eine Gruppe begünstigt zweistufigen Aufbau. Der Konstruktor bringt lediglich das Objekt in einen Schlafzustand, in dem sie jede Arbeit zu tun verweigert. Es gibt eine zusätzliche Funktion, die die tatsächliche Initialisierung der Fall ist.

Ich habe nie verstanden, die Gründe für diesen Ansatz. Ich bin fest in der Gruppe, die eine einstufige Konstruktion unterstützt, wo das Objekt vollständig initialisierte und verwendbar nach dem Bau ist.

Ein-Schritt-Konstrukteure sollten werfen, wenn sie nicht vollständig um das Objekt zu initialisieren. Wenn das Objekt nicht initialisiert werden kann, muss es nicht existieren werden dürfen, so muss der Konstruktor werfen.

Andere Tipps

Eric Lippert sagt gibt es 4 Arten von Ausnahmen.

  • Fatal Ausnahmen sind nicht deine Schuld, können Sie sie nicht verhindern können, und Sie können von ihnen nicht vernünftig aufzuräumen.
  • Boneheaded Ausnahmen Ihre eigene verflixte Fehler sind, haben Sie sie verhindern können und deshalb in Ihrem Code sind sie Fehler.
  • Vexing Ausnahmen sind das Ergebnis einer unglücklichen Design-Entscheidungen. Vexing Ausnahmen werden in einem vollständig nicht-außergewöhnlichen Umstand geworfen und damit gefangen werden muss, und die ganze Zeit behandelt werden.
  • Und schließlich erscheinen exogene Ausnahmen etwas wie ärgerliche Ausnahmen sein, außer dass sie nicht das Ergebnis einer unglücklichen Design-Entscheidungen sind. Vielmehr sind sie das Ergebnis der unordentlichen äußeren Realitäten Auftreffen auf Ihre schöne, knackige Programmlogik.

Ihr Konstruktor soll nie zu einer schwerwiegenden Ausnahme auf eigenem werfen, aber Code es zu einer schwerwiegenden Ausnahme verursachen kann ausführt. So etwas wie „out of memory“ ist nicht etwas, das Sie kontrollieren können, aber wenn es in einem Konstruktor auftritt, hey, es passiert.

Boneheaded Ausnahmen sollten niemals in Ihrem Code auftreten, so dass sie direkt aus.

Vexing Ausnahmen (das Beispiel ist Int32.Parse()) sollte nicht von Konstrukteuren geworfen werden, weil sie keine nicht-außergewöhnliche Umstände hat.

Schließlich sollten exogene Ausnahmen vermieden werden, aber wenn Sie etwas in Ihrem Konstruktor tun, die auf äußere Umstände (wie das Netzwerk oder Dateisystem) abhängt, wäre es angemessen, eine Ausnahme zu werfen.

Es ist im Allgemeinen nichts von der Scheidung Objektinitialisierung von der Konstruktion gewonnen werden. RAH ist richtig, ein erfolgreicher Aufruf an den Konstruktor sollte entweder zu einem vollständig initialisiert Live-Objekt oder es ausfallen soll, und Alle Ausfälle an jedem Punkt in jedem Codepfad sollten immer eine Ausnahme werfen. Sie gewinnen nichts durch Verwendung eines separaten init () -Methode außer zusätzliche Komplexität auf einer bestimmten Ebene. Der Ctor Vertrag sollte entweder es gibt ein funktionales gültiges Objekt oder es reinigt nach mir und wirft.

Betrachten Sie, wenn Sie sich über eine separate init-Methode implementieren, können Sie noch hat es zu nennen. Es wird immer noch das Potenzial hat, Ausnahmen zu werfen, sie haben immer noch behandelt werden und sie so gut wie immer ohnehin unmittelbar nach dem Konstruktor aufgerufen werden, es sei denn Sie jetzt 4 mögliche Objektzustände haben anstelle von 2 (IE, konstruiert, initialisiert, nicht initialisierte, und scheiterte vs nur gültig und nicht existent).

Auf jeden Fall habe ich in 25 Jahren OO Entwicklung Fällen laufen über, wo es wie ein separates init-Methode scheint wäre ‚ein Problem lösen‘ sind Konstruktionsfehler. Wenn Sie ein Objekt nicht brauchen JETZT dann sollten Sie nicht es jetzt werden die Konstruktion, und wenn Sie es tun müssen, jetzt müssen Sie es initialisiert. KISS sollte der Grundsatz gefolgt immer sein, zusammen mit dem einfachen Konzept, dass das Verhalten, Staat und API jeder Schnittstelle widerspiegeln sollte, was das Objekt tut, nicht wie sie es tut, Client-Code nicht einmal bewusst sein sollte, dass das Objekt, jede Art hat internen Zustand, die Initialisierung verletzt, so dass die init nach Mustern dieses Prinzip erfordert.

Wegen all der Probleme, die eine teilweise erstellte Klasse führen kann, würde ich nie sagen.

Wenn Sie etwas während des Aufbaus validieren müssen, machen den Konstruktor private und eine öffentliche statische Factory-Methode definieren. Das Verfahren kann werfen, wenn etwas ist ungültig. Aber wenn alles klar geht, ruft er den Konstruktor, die nicht garantiert ist zu werfen.

Ein Konstruktor sollte eine Ausnahme auslösen, wenn es nicht in der Lage den Bau des Objekts abzuschließen.

Zum Beispiel, wenn der Konstruktor soll 1024 KB RAM zuweisen, und es scheitert, dies zu tun, sollte es eine Ausnahme auslösen, so der Anrufer des Konstrukteurs weiß, dass das Objekt nicht bereit ist, verwendet zu werden, und es wird ein Fehler irgendwo, der behoben werden muss.

Objekte, die Halb initialisiert sind und halbtot nur Probleme und Probleme verursachen, da es wirklich keine Möglichkeit für die Anrufer zu wissen. Ich habe lieber mein Konstruktor wirft einen Fehler, wenn die Dinge schief gehen, als wenn man auf der Programmierung verlassen, um einen Anruf an die isOK () Funktion auszuführen, die wahr oder falsch zurück.

Es ist immer ziemlich vertrackt, vor allem wenn man die Zuteilung der Ressourcen in einem Konstruktor ist; Je nach Sprache wird nicht der Destruktor aufgerufen, so dass Sie manuell Bereinigung benötigen. Es hängt davon ab, wie wenn ein Objekt der Lebensdauer in Ihrer Sprache beginnt.

Das einzige Mal, dass ich wirklich getan habe, ist es, wenn es ein Sicherheitsproblem irgendwo gewesen, die das Objekt bedeutet nicht soll, anstatt kann nicht erstellt werden.

Es ist sinnvoll für ein Konstruktor eine Ausnahme zu werfen, solange sie sich richtig aufräumt. Wenn Sie die RAII Paradigma (Resource Acquisition Is Initialization) folgen dann ist für einen Konstruktor recht häufig sinnvolle Arbeit zu tun; eine gut geschriebene Konstruktor wiederum nach selbst aufzuräumen, wenn es nicht vollständig initialisiert werden kann.

Soweit ich das beurteilen kann, niemand eine ziemlich offensichtliche Lösung präsentiert, die das Beste aus beide einer einstufigen und zweistufigen Konstruktion verkörpert.

. Hinweis: Diese Antwort geht davon aus C #, aber die Prinzipien können in den meisten Sprachen angewendet werden

Als erstes werden die Vorteile beider:

Einzeitiges

Ein-Schritt-Bau zugute kommt uns durch Objekte aus bestehenden in einem ungültigen Zustand zu verhindern, so dass alle Arten von fehlerhafter Zustandsverwaltung zu verhindern und alle Fehler, die mit ihm gekommen. Allerdings lässt es einige von uns das Gefühl komisch, weil wir Ausnahmen werfen nicht unsere Konstrukteure wollen, und manchmal ist das, was wir tun müssen, wenn die Initialisierung Argumente sind ungültig.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}

Zweistufige über Validierungsmethode

Zweistufige Konstruktion profitieren wir, indem unsere Validierung außerhalb des Konstruktor ausgeführt werden, und verhindert daher die Notwendigkeit für Ausnahmen im Konstruktor werfen. Aber es lässt uns mit „ungültig“ Instanzen, was bedeutet, es gibt Zustand müssen wir für die Instanz verfolgen und zu verwalten, oder wir werfen sie sofort nach dem Heap-Zuordnung entfernt. Es stellt sich die Frage: Warum sind wir eine Heapzuordnung und somit Speichersammlung durchgeführt wird, auf einem Objekt, das wir verwenden nicht einmal am Ende

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}

Einstufige über privaten Konstruktor

Wie können wir also halt Ausnahmen aus unseren Konstrukteuren, und verhindern, dass sie Heapzuordnung auf Objekte aus der Durchführung, die sofort verworfen werden? Es ist ziemlich einfach. Wir den Konstruktor privat und erstellen Instanzen über eine statische Methode machen bezeichnete eine Instanziierung durchzuführen, und daher Heap-Zuordnung, nur nach Validierung

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}

Async einstufiges über privaten Konstruktor

Neben den oben genannten Validierung und Heap-Zuweisung Prävention Vorteilen, die bisherige Methodik bietet uns eine andere raffinierte Vorteil: Asynchron-Unterstützung. Das ist praktisch, wenn mit mehrstufiger Authentifizierung tun haben, wie zum Beispiel, wenn Sie benötigen, bevor Sie Ihre API einen Träger Token abzurufen. Auf diese Weise, die Sie nicht mit einem ungültigen „abgemeldeten“ API-Client am Ende, und stattdessen können Sie einfach das API-Client neu erstellen, wenn Sie eine Berechtigungsfehlermeldung beim Versuch, eine Anfrage auszuführen.

public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}

Die Nachteile dieser Methode sind nur wenige, in meiner Erfahrung.

Im Allgemeinen mit dieser Methode bedeutet, dass Sie nicht mehr die Klasse als DTO verwenden können, denn ohne einen öffentlichen Standardkonstruktor auf ein Objekt deserialisiert hart ist, am besten. wenn Sie das Objekt als DTO wurden jedoch verwenden, sollten Sie nicht wirklich werden Validieren das Objekt selbst, sondern invaliding die Werte auf dem Objekt, wie Sie sie zu nutzen versuchen, da technisch die Werte nicht „ungültig“ in Bezug an den DTO.

Es bedeutet auch, dass Sie Fabrik am Ende der Schaffung werden Methoden oder Klassen, wenn Sie ein IOC-Container müssen, damit das Objekt erstellen, da sonst der Behälter nicht wissen, wie das Objekt zu instanziiert. Allerdings endet in vielen Fällen die Fabrikmethoden bis seiend ein Create Methoden selbst.

Siehe C ++ FAQ Abschnitte 17,2 und < a href = "http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.4" rel = "nofollow noreferrer"> 17,4 .

In der Regel habe ich diesen Code gefunden, die leichter zu tragen ist und die Ergebnisse erhalten, wenn Konstrukteure geschrieben werden, so dass sie nicht scheitern, und Code, der in einem separaten Verfahren platziert fehlschlagen kann, die einen Fehlercode zurück und verlassen das Objekt in ein inerter Zustand.

Wenn Sie UI-Steuerelemente schreiben (ASPX, WinForms, WPF, ...) Sie vermeiden sollten Ausnahmen im Konstruktor werfen, weil der Designer (Visual Studio) können sie nicht behandeln, wenn es Ihre Kontrollen erstellt. Kennen Sie Ihre Steuer-Lebenszyklus (Control-Ereignisse) und verwenden faul Initialisierung, wo immer möglich.

Beachten Sie, wenn Sie eine Ausnahme in einem initializer werfen, werden Sie undichte am Ende, wenn jeder Code der [[[MyObj alloc] init] autorelease] Muster verwendet, da die Ausnahme wird die Autofreigabe überspringt.

Sehen Sie diese Frage:

Wie verhindern Sie Lecks beim Anheben eine Ausnahme in init?

Sie sollten unbedingt eine Ausnahme von einem Konstruktor werfen, wenn Sie nicht auf ein gültiges Objekt erstellen. Auf diese Weise können Sie die richtige Invarianten in Ihrer Klasse liefern.

In der Praxis kann man sehr vorsichtig sein müssen. Denken Sie daran, dass in C ++, wird der Destruktor nicht aufgerufen werden, wenn Sie also werfen nach der Zuteilung von Ressourcen, müssen Sie sorgfältig darauf achten, dass richtig zu behandeln!

Diese Seite hat eine gründliche Diskussion über die Situation in C ++.

eine Ausnahme aus, wenn Sie nicht auf das Objekt im Konstruktor zu initialisieren, ein Beispiel sind illegal Argumente.

Als Faustregel eine Ausnahme sollte immer so schnell wie möglich geworfen werden, da es einfacher Debuggen macht, wenn die Quelle des Problems die Methode näher signalisiert etwas nicht in Ordnung ist.

Werfen eine Ausnahme während der Bauphase ist eine gute Möglichkeit, Ihren Code Weg komplexer zu machen. Dinge, die plötzlich hart einfach erscheinen würde. Zum Beispiel, sagen wir, Sie haben einen Stapel. Wie Pop Sie den Stapel und den oberen Wert zurückgeben? Nun, wenn die Gegenstände in dem Stapel in ihren Konstrukteuren werfen können (Aufbau des temporären dem Anrufer zurückzukehren), können Sie nicht garantieren, dass Sie keine Daten verlieren (Dekrement Stapelzeiger, konstruieren Rückgabewert Copykonstruktor Wert Verwendung in Stapel, der wirft, und haben jetzt einen Stapel, der nur ein Element verloren)! Aus diesem Grund ist std :: stack :: pop keinen Wert zurück, und Sie haben std anrufen :: stapeln :: top.

Dieses Problem gut beschrieben ist hier , überprüft Punkt 10, Schreiben Ausnahme-sichere Code.

Der üblicher Vertrag in OO ist, dass die Objektmethoden tun eigentlich funktionieren.

So als corrolary, nie ein Zombie-Objekt zurückgeben einen Konstruktor / init bilden.

Ein Zombie nicht funktionsfähig ist und interne Komponenten fehlen. Nur eine Null-Zeiger-Ausnahme warten geschehen.

Ich habe erste Zombies in Objective C, vor vielen Jahren.

Wie alle Faustregeln gibt es eine „Ausnahme“.

Es ist durchaus möglich, dass ein spezifische Schnittstelle kann einen Vertrag hat, der sagt, dass es existiert ein Verfahren, „Initialisieren“, die auf eine Ausnahme thron erlaubt. Dass ein Objekt dieses Interface + Einrichtung reagiert möglicherweise nicht korrekt auf alle Anrufe außer Eigentum Setter bis initialize aufgerufen wurde. Ich habe diese für Gerätetreiber in einem OO-Betriebssystem während des Startvorganges, und es war praktikabel.

In der Regel wollen Sie nicht Zombie-Objekte. In Sprachen wie Smalltalk mit werden die Dinge ein wenig spritzig-buzzy, aber übermäßigen Gebrauch von werden ist zu schlecht Stil. Werden Sie kann in ein anderes Objekt eine Objektänderung in-situ, so gibt es keine Notwendigkeit für Umschlag-Wrapper (Advanced C ++) oder das Strategie-Muster (GOF).

Ich kann nicht best practice in Objective-C-Adresse, aber in C ++ es ist in Ordnung für ein Konstruktor eine Ausnahme zu werfen. Vor allem, da gibt es keinen anderen Weg, um sicherzustellen, dass ein Ausnahmezustand im Bau angetroffen wird berichtet, ohne Rückgriff auf eine isOK () -Methode aufgerufen wird.

Die Funktion Try-Block-Funktion wurde speziell für die Unterstützung Ausfälle in Konstruktor elemente Initialisieren bestimmt (obwohl es auch für reguläre Funktionen verwendet werden kann). Es ist die einzige Art und Weise zu ändern oder die Ausnahmeinformationen zu bereichern, die geworfen werden. Aber wegen seiner ursprünglichen Gestaltung Zweck (Verwendung in Konstrukteuren) ist es nicht die Ausnahme zulässt durch eine leere catch () -Klausel geschluckt werden.

Ja, wenn der Konstruktor nicht einen seines inneren Teils zu bauen, kann es sein - nach Wahl - seine Verantwortung (und in bestimmten Sprache zu erklären) zu werfen ein explizite Ausnahme , im Konstruktor Dokumentation zur Kenntnis genommen.

Dies ist nicht die einzige Option: Es könnte den Konstruktor zu beenden und ein Objekt bauen, aber mit einer Methode ‚isCoherent ()‘ false zurückgibt, um einen inkohärenten Zustand in der Lage sein, um zu signalisieren (dass in bestimmten Fällen bevorzugt sein kann, , um eine brutale Unterbrechung der Ausführung Workflow aufgrund einer Ausnahme)
zu vermeiden Achtung: wie in seinem Kommentar von EricSchaefer sagte, dass eine gewisse Komplexität auf die Unit-Tests bringen kann (ein Wurf kann erhöhen die zyklomatische Komplexität der Funktion der Bedingung durch, dass es)

Wenn es wegen des Anrufers nicht (wie ein Null-Argument vom Anrufer zur Verfügung gestellt, in dem der genannten Konstruktor ein Nicht-Null-Argument erwartet), der Konstruktor wird ohnehin eine ungeprüfte Laufzeit Ausnahme aus.

Ich bin nicht sicher, dass jede Antwort völlig sprachunabhängig sein kann. Einige Sprachen verarbeiten Ausnahmen und Speicherverwaltung anders.

Ich habe vor unter Coding-Standards gearbeitet Ausnahmen erfordern nie und nur Fehlercodes auf initializers verwendet werden, weil die Entwickler durch die Sprache schlecht verbrannt worden waren Behandlung von Ausnahmen. Sprachen ohne Garbage Collection wird Griff Heap und Stack sehr unterschiedlich, was für nicht RAII Objekte Materie kann. Es ist jedoch wichtig, dass ein Team konsequent sein entscheiden, so dass sie standardmäßig wissen, ob sie benötigen initializers nach Konstrukteuren zu nennen. Alle Methoden (einschließlich Konstruktoren) sollten ebenfalls gut dokumentiert werden, was Ausnahmen sie werfen können, so Anrufer wissen, wie sie zu behandeln.

Ich bin generell für eine Konstruktion einstufig, wie es leicht zu vergessen, um ein Objekt zu initialisieren, aber es gibt viele Ausnahmen, dass.

  • Ihre Sprachunterstützung für Ausnahmen ist nicht sehr gut.
  • Sie haben ein dringendes Design Grund noch new und delete verwenden
  • Ihre Initialisierung ist prozessorintensiv und sollte async auf den Thread ausführen, die das Objekt erstellt hat.
  • Sie erstellen eine DLL, die Ausnahmen werfen kann außerhalb Schnittstelle ist auf eine Anwendung eine andere Sprache. In diesem Fall kann es nicht so sehr eine Frage der keine Ausnahmen zu werfen, sondern sicherstellen, dass sie vor der öffentlichen Schnittstelle gefangen werden. (Sie können C ++ Ausnahmen in C # fangen, aber es gibt Reifen zu springen durch.)
  • Statische Konstruktoren (C #)

Die Frage des OP hat einen „sprachunabhängig“ tag ... diese Frage kann nicht sicher sein, die gleiche Art und Weise für alle Sprachen / Situationen beantwortet.

Das folgende C # Beispiels der Klassenhierarchie führt in Konstruktor der Klasse B, einen sofortigen Aufruf der Klasse A der IDisposeable.Dispose beim Verlassen des using Hauptes Skipping, explizite Entsorgung von Klasse A Ressourcen zu überspringen.

Wenn zum Beispiel der Klasse A ein Socket im Bau geschaffen hatte, zu einer Netzwerk-Ressource verbunden ist, so würde wahrscheinlich immer noch der Fall nach dem using Block (ein relativ versteckt Anomalie).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}

streng vom Standpunkt Java Sprechen, jedes Mal, wenn Sie einen Konstruktor mit illegalen Werten zu initialisieren, sollte es eine Ausnahme werfen. Auf diese Weise es nicht in einem schlechten Zustand konstruiert bekommt.

Für mich ist es eine etwas philosophische Design-Entscheidung.

Es ist sehr schön Instanzen zu haben, die gültig sind, solange sie existieren, von Ctor Zeit an. Für viele nicht-triviale Fälle erfordert dies nicht gemacht werden kann Ausnahmen von dem Ctor, wenn eine Speicher / Ressourcenzuordnung zu werfen.

Einige andere Ansätze sind die init () Methode, die mit einigen Fragen der eigenen kommt. Eines davon ist die Gewährleistung init () wird tatsächlich aufgerufen.

Eine Variante einen faulen Ansatz wird mit automatisch init () aufzurufen, das erste Mal ein Accessor / Mutator aufgerufen wird, aber das erfordert jede potentielle Anrufer zu befürchten zu haben, über das Objekt gültig ist. (Im Gegensatz zu dem „es existiert, damit es gültig Philosophie“).

Ich habe verschiedenes vorgeschlagene Design-Muster gesehen auch mit diesem Thema zu befassen. Wie in der Lage ein anfängliches Objekt über Ctor zu schaffen, aber init (), um Ihre Hände auf einem enthalten ist, initialisiert Objekt mit accesors / Mutatoren.

anrufen zu müssen

Jeder Ansatz hat seine Höhen und Tiefen; Ich habe alle diese erfolgreich eingesetzt. Wenn Sie nicht bereit zu bedienende Objekte machen ab dem Zeitpunkt ihrer Erstellung, dann empfehle ich eine schwere Dosis von behauptet oder Ausnahmen sicher, dass Benutzer nicht vor init () macht interagieren.

Nachtrag

Ich schrieb von einer C ++ Programmierer Perspektive. Außerdem gehe ich davon sind Sie richtig das RAII Idiom mit Ressourcen umgehen freigegeben werden, wenn Ausnahmen ausgelöst werden.

Ich lerne gerade Objective C, so kann ich nicht wirklich aus Erfahrung sprechen, aber ich habe darüber in Apples docs lesen.

http://developer.apple.com/documentation /Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html

Nicht nur wird es Ihnen sagen, wie die Frage behandeln Sie gefragt, aber es macht einen guten Job es auch zu erklären.

Mit Fabriken oder Factory-Methoden für alle Objekterstellung, können Sie ungültige Objekte vermeiden, ohne Ausnahmen von Konstrukteuren zu werfen. Die Erstellungsmethode sollte das angeforderte Objekt zurückgeben, wenn es in der Lage ist, eines zu erstellen, oder null, wenn es nicht ist. Sie verlieren einen wenig Flexibilität Konstruktionsfehler in dem Benutzer einer Klasse in der Handhabung, da null Rückkehr Ihnen nicht sagen, was in der Objekterstellung falsch gelaufen ist. Aber es vermeidet auch die Komplexität von mehreren Exception-Handler Hinzufügen jedes Mal, wenn Sie ein Objekt anfordern, und das Risiko zu fangen Ausnahmen sollten Sie nicht behandeln.

Der beste Rat, den ich über Ausnahmen gesehen habe, ist eine Ausnahme, wenn zu werfen, und nur dann, wenn die Alternative ist das Versagen einen Beitrag Bedingung zu erfüllen oder eine invariante aufrecht zu erhalten.

Dieser Rat ersetzt eine unklare subjektive Entscheidung (es ist ein gute Idee ) mit einer technischen, präzisen Frage basierend auf Designentscheidungen (Invarianten und Nachbedingungen) sollten Sie bereits gemacht haben.

Konstrukteurs sind nur eine bestimmte, aber nicht speziell, Fall für die Beratung. So wird die Frage, welche Invarianten sollte eine Klasse? Die Befürworter eines separaten Initialisierungsmethode, um nach dem Bau genannt werden, sind darauf hindeutet, dass die Klasse zwei oder mehr Betriebsart , mit einem unready Modus nach dem Bau und mindestens ein < em> bereit Modus, eingegeben nach der Initialisierung. Das ist eine zusätzliche Komplikation, aber akzeptabel, wenn die Klasse sowieso mehrere Betriebsmodi. Es ist schwer zu sehen, wie diese Komplikation lohnt, wenn die Klasse sonst nicht Betriebsart hat.

Beachten Sie, dass in eine separate Initialisierungsmethode einrichten Drückens Sie nicht ermöglicht, Ausnahmen geworfen zu werden zu vermeiden. Ausnahmen, die Ihr Konstruktor könnte geworfen hat, werden nun durch die Initialisierungsmethode geworfen werden. Alle nützlichen Methoden Ihrer Klasse müssen Ausnahmen auslösen, wenn sie einem nicht initialisierten Objekt aufgerufen werden.

Beachten Sie auch, dass die Möglichkeit, Ausnahmen zu vermeiden, indem Ihr Konstruktor geworfen wird, ist lästig, und in vielen Fällen unmöglich in vielen Standardbibliotheken. Dies liegt daran, die Designer dieser Bibliotheken glauben, dass Ausnahmen von Konstrukteuren ist eine gute Idee zu werfen. Insbesondere ist jede Operation, die eine nicht gemeinsam nutzbare oder finite Ressource (wie Zuweisung Speicher) zu erlangen versucht, kann fehlschlagen, und das Versagen in OO Sprachen und Bibliotheken wird typischerweise durch Auslösen einer Ausnahme angegeben.

ctors sollen keine „smart“ Dinge tun, damit eine Ausnahme werfen ohnehin nicht benötigt wird. Verwenden Sie einen Init () oder Setup () -Methode, wenn Sie etwas komplizierteren Objekt-Setup ausgeführt werden sollen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top