it-swarm.it

Quando è giusto che un costruttore lanci un'eccezione?

Quando è giusto che un costruttore lanci un'eccezione? (O nel caso dell'Obiettivo C: quando è giusto che un iniziatore restituisca zero?)

Mi sembra che un costruttore dovrebbe fallire - e quindi rifiutare di creare un oggetto - se l'oggetto non è completo. Ad esempio, il costruttore dovrebbe avere un contratto con il suo chiamante per fornire un oggetto funzionale e funzionante su quali metodi possono essere chiamati in modo significativo? È ragionevole?

197
Mark R Lindsey

Il compito del costruttore è di portare l'oggetto in uno stato utilizzabile. Ci sono fondamentalmente due scuole di pensiero su questo.

Un gruppo favorisce la costruzione in due fasi. Il costruttore porta semplicemente l'oggetto in uno stato dormiente in cui si rifiuta di fare qualsiasi lavoro. C'è una funzione aggiuntiva che esegue l'inizializzazione effettiva.

Non ho mai capito il ragionamento alla base di questo approccio. Sono saldamente nel gruppo che supporta la costruzione in una fase, in cui l'oggetto è completamente inizializzato e utilizzabile dopo la costruzione.

I costruttori a uno stadio dovrebbero lanciare se non riescono a inizializzare completamente l'oggetto. Se l'oggetto non può essere inizializzato, non deve essere consentito l'esistenza, quindi il costruttore deve lanciare.

261
Sebastian Redl

dice Eric Lippert ci sono 4 tipi di eccezioni.

  • Le eccezioni fatali non sono colpa tua, non puoi prevenirle e non puoi eliminarle sensibilmente.
  • Le eccezioni di Boneheaded sono colpa tua, potresti averle evitate e quindi sono bug nel tuo codice.
  • Eccezionali eccezioni sono il risultato di sfortunate decisioni di progettazione. Eccezioni fastidiose vengono lanciate in una circostanza del tutto non eccezionale, e quindi devono essere catturate e gestite continuamente.
  • Infine, le eccezioni esogene sembrano essere in qualche modo simili a fastidiose eccezioni, tranne per il fatto che non sono il risultato di scelte di design sfortunate. Piuttosto, sono il risultato di disordinate realtà esterne che incidono sulla tua logica del programma bella e nitida.

Il costruttore non dovrebbe mai generare un'eccezione fatale da solo, ma il codice che esegue può causare un'eccezione fatale. Qualcosa come "memoria esaurita" non è qualcosa che puoi controllare, ma se si verifica in un costruttore, ehi, succede.

Eccezioni senza ossa non dovrebbero mai verificarsi in nessuno dei tuoi codici, quindi vanno subito bene.

Eccezioni fastidiose (l'esempio è Int32.Parse()) non dovrebbero essere lanciate dai costruttori, perché non hanno circostanze non eccezionali.

Infine, dovrebbero essere evitate eccezioni esogene, ma se stai facendo qualcosa nel tuo costruttore che dipende da circostanze esterne (come la rete o il filesystem), sarebbe opportuno lanciare un'eccezione.

Link di riferimento: https://blogs.msdn.Microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

56
Jacob Krall

Non c'è generalmente nulla da guadagnare separando l'inizializzazione dell'oggetto dalla costruzione. RAII è corretto, una chiamata riuscita al costruttore dovrebbe comportare un oggetto live completamente inizializzato o dovrebbe fallire e ALL fallimenti in qualsiasi punto di qualsiasi codice il percorso dovrebbe sempre generare un'eccezione. Non si ottiene nulla usando un metodo init () separato, tranne una complessità aggiuntiva a un certo livello. Il contratto ctor dovrebbe essere o restituisce un oggetto funzionale valido o pulisce dopo se stesso e genera.

Considera, se implementi un metodo init separato, devi ancora chiamarlo. Avrà comunque il potenziale di generare eccezioni, devono comunque essere gestite e devono sempre essere quasi sempre chiamate immediatamente dopo il costruttore, tranne per il fatto che ora hai 4 possibili stati oggetto invece di 2 (IE, costruito, inizializzato, non inizializzato, e fallito vs solo valido e inesistente).

In ogni caso mi sono imbattuto in 25 anni di OO casi di sviluppo in cui sembra che un metodo init separato "risolva qualche problema" sono difetti di progettazione. Se non hai bisogno di un oggetto ORA, allora non dovresti costruirlo ora, e se ne hai bisogno ora, allora devi inizializzarlo. KISS dovrebbe sempre essere il principio seguito, insieme al semplice concetto che il comportamento, stato e l'API di qualsiasi interfaccia dovrebbe riflettere COSA fa l'oggetto, non COME lo fa, il codice client non dovrebbe nemmeno essere consapevole del fatto che l'oggetto ha qualsiasi tipo di stato interno che richiede l'inizializzazione, quindi il modello init after viola questo principio.

30
Alhazred

A causa di tutti i problemi che una classe parzialmente creata può causare, direi mai.

Se è necessario convalidare qualcosa durante la costruzione, rendere privato il costruttore e definire un metodo di fabbrica statico pubblico. Il metodo può essere lanciato se qualcosa non è valido. Ma se tutto verifica, chiama il costruttore, che è garantito per non lanciare.

6
Michael L Perry

Un costruttore dovrebbe lanciare un'eccezione quando non è in grado di completare la costruzione di detto oggetto.

Ad esempio, se si suppone che il costruttore assegni 1024 KB di RAM e non riesca a farlo, dovrebbe generare un'eccezione, in questo modo il chiamante del costruttore sa che l'oggetto non è pronto per essere utilizzato e c'è un errore da qualche parte che deve essere riparato.

Gli oggetti che sono metà inizializzati e metà morti causano solo problemi e problemi, in quanto il chiamante non ha davvero modo di saperlo. Preferirei che il mio costruttore lanciasse un errore quando le cose vanno male, piuttosto che dover fare affidamento sulla programmazione per eseguire una chiamata alla funzione isOK () che restituisce vero o falso.

5
Denice

È sempre piuttosto complicato, soprattutto se stai allocando risorse all'interno di un costruttore; a seconda della tua lingua il distruttore non verrà chiamato, quindi devi ripulirlo manualmente. Dipende da come inizia la vita di un oggetto nella tua lingua.

L'unica volta che l'ho fatto davvero è quando c'è stato un problema di sicurezza da qualche parte che significa che l'oggetto non dovrebbe, piuttosto che non può essere creato.

4
blowdart

È ragionevole per un costruttore lanciare un'eccezione purché si ripulisca correttamente. Se segui il paradigma RAII (l'acquisizione delle risorse è inizializzazione), allora è abbastanza comune per un costruttore fare un lavoro significativo; un costruttore ben scritto a sua volta ripulirà se stesso se non può essere completamente inizializzato.

4
Matt Dillard

Per quanto ne so, nessuno presenta una soluzione abbastanza ovvia che incarna il meglio della costruzione sia a uno che a due stadi.

nota: Questa risposta presuppone C #, ma i principi possono essere applicati nella maggior parte delle lingue.

Innanzitutto, i vantaggi di entrambi:

One-Stage

La costruzione in una fase ci avvantaggia impedendo l'esistenza di oggetti in uno stato non valido, impedendo così ogni tipo di gestione errata dello stato e tutti i bug che ne derivano. Tuttavia, alcuni di noi si sentono strani perché non vogliamo che i nostri costruttori lancino eccezioni, e talvolta questo è ciò che dobbiamo fare quando gli argomenti di inizializzazione non sono validi.

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;
    }
}

Due fasi tramite metodo di validazione

La costruzione in due fasi ci avvantaggia consentendo l'esecuzione della nostra convalida al di fuori del costruttore e quindi impedisce la necessità di generare eccezioni all'interno del costruttore. Tuttavia, ci lascia con istanze "non valide", il che significa che c'è stato che dobbiamo tracciare e gestire per l'istanza, oppure la gettiamo via immediatamente dopo l'allocazione dell'heap. Sorge la domanda: perché stiamo eseguendo un'allocazione dell'heap, e quindi una raccolta di memoria, su un oggetto che non finiamo nemmeno per usare?

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));
        }
    }
}

Singolo stadio tramite costruttore privato

Quindi, come possiamo evitare le eccezioni dai nostri costruttori e impedirci di eseguire l'allocazione dell'heap su oggetti che verranno immediatamente scartati? È piuttosto semplice: rendiamo privato il costruttore e creiamo istanze tramite un metodo statico designato per eseguire un'istanza, e quindi allocazione di heap, solo dopo validazione.

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 Single-Stage tramite costruttore privato

A parte i benefici di prevenzione della convalida e dell'allocazione di cui sopra, la metodologia precedente ci offre un altro vantaggio elegante: il supporto asincrono. Ciò è utile quando si ha a che fare con l'autenticazione a più fasi, ad esempio quando è necessario recuperare un token di accesso prima di utilizzare l'API. In questo modo, non si finisce con un client API "disconnesso" non valido e si può semplicemente ricreare il client API se si riceve un errore di autorizzazione mentre si tenta di eseguire una richiesta.

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);
        }
    }
}

Gli aspetti negativi di questo metodo sono pochi, secondo la mia esperienza.

In genere, l'utilizzo di questa metodologia significa che non è più possibile utilizzare la classe come DTO perché deserializzare un oggetto senza un costruttore predefinito pubblico è, nella migliore delle ipotesi, difficile. Tuttavia, se si stesse utilizzando l'oggetto come DTO, non si dovrebbe realmente convalidare l'oggetto stesso, ma piuttosto invalidare i valori sull'oggetto mentre si tenta di usarli, dal momento che tecnicamente i valori non sono "non validi" per quanto riguarda al DTO.

Significa anche che finirai per creare metodi o classi di fabbrica quando devi consentire un contenitore IOC per creare l'oggetto, poiché altrimenti il ​​contenitore non saprà come creare un'istanza dell'oggetto. Tuttavia, in molti casi i metodi factory finiscono per essere uno dei metodi Create stessi.

4
cwharris

Si noti che se si genera un'eccezione in un inizializzatore, si finirà per perdere se un codice utilizza [[[MyObj alloc] init] autorelease] pattern, poiché l'eccezione salterà il rilascio automatico.

Vedi questa domanda:

Come evitare le perdite quando si solleva un'eccezione in init?

3
stevex

Se stai scrivendo UI-Controls (ASPX, WinForms, WPF, ...) dovresti evitare di generare eccezioni nel costruttore perché il designer (Visual Studio) non può gestirle quando crea i tuoi controlli. Conosci il tuo ciclo di vita del controllo (eventi di controllo) e usa l'inizializzazione lazy laddove possibile.

3
Nick

Vedi C++ FAQ sezioni 17.2 e 17.4 .

In generale, ho trovato quel codice che è più facile da trasportare e mantenere i risultati se i costruttori sono scritti in modo che non falliscano, e il codice che può fallire viene inserito in un metodo separato che restituisce un codice di errore e lascia l'oggetto in uno stato inerte .

3
moonshadow

Genera un'eccezione se non riesci a inizializzare l'oggetto nel costruttore, un esempio sono argomenti illegali.

Come regola generale, un'eccezione dovrebbe sempre essere generata il più presto possibile, poiché semplifica il debug quando l'origine del problema è più vicina al metodo che segnala che qualcosa non va.

2
user14070

Dovresti assolutamente lanciare un'eccezione da un costruttore se non riesci a creare un oggetto valido. Ciò ti consente di fornire invarianti adeguati nella tua classe.

In pratica, potresti dover stare molto attento. Ricorda che in C++ non verrà chiamato il distruttore, quindi se lanci dopo aver allocato le tue risorse, devi fare molta attenzione per gestirlo correttamente!

Questa pagina ha una discussione approfondita della situazione in C++.

2
Luke Halliwell

Non posso affrontare le migliori pratiche in Objective-C, ma in C++ va bene per un costruttore lanciare un'eccezione. Soprattutto perché non esiste un altro modo per garantire che venga segnalata una condizione eccezionale incontrata durante la costruzione senza ricorrere all'invocazione di un metodo isOK ().

La funzione blocco di prova funzione è stata progettata specificamente per supportare errori nell'inizializzazione membro-membro del costruttore (sebbene possa essere utilizzata anche per funzioni regolari). È l'unico modo per modificare o arricchire le informazioni sull'eccezione che verranno generate. Ma a causa del suo scopo di progettazione originale (uso nei costruttori), non consente che l'eccezione venga inghiottita da una clausola catch () vuota.

1
mlbrock

Non sono sicuro che qualsiasi risposta possa essere totalmente indipendente dalla lingua. Alcune lingue gestiscono le eccezioni e la gestione della memoria in modo diverso.

Ho lavorato in precedenza con gli standard di codifica che richiedono che le eccezioni non vengano mai utilizzate e che solo i codici di errore sugli inizializzatori, poiché gli sviluppatori erano stati masterizzati dal linguaggio che gestiva male le eccezioni. Le lingue senza garbage collection gestiranno l'heap e lo stack in modo molto diverso, il che può importare per gli oggetti non RAII. È importante tuttavia che un team decida di essere coerente in modo da sapere per impostazione predefinita se è necessario chiamare gli inizializzatori dopo i costruttori. Tutti i metodi (inclusi i costruttori) dovrebbero anche essere ben documentati sulle eccezioni che possono generare, così i chiamanti sanno come gestirli.

Sono generalmente a favore di una costruzione a singolo stadio, poiché è facile dimenticare di inizializzare un oggetto, ma ci sono molte eccezioni a questo.

  • Il tuo supporto linguistico per le eccezioni non è molto buono.
  • Hai un motivo urgente per usare ancora new e delete
  • L'inizializzazione richiede un uso intensivo del processore e dovrebbe essere eseguita in modo asincrono sul thread che ha creato l'oggetto.
  • Stai creando un DLL che potrebbe generare eccezioni al di fuori della sua interfaccia a un'applicazione che utilizza una lingua diversa. In questo caso potrebbe non essere un problema di non generare eccezioni, ma assicurandoti che vengono catturati prima dell'interfaccia pubblica (puoi trovare eccezioni C++ in C #, ma ci sono dei cerchi da saltare).
  • Costruttori statici (C #)
1
Denise Skidmore

Sì, se il costruttore non riesce a costruire una sua parte interna, può essere - per scelta - la sua responsabilità di gettare (e in una certa lingua per dichiarare) un eccezione esplicita , debitamente annotato nella documentazione del costruttore .

Questa non è l'unica opzione: potrebbe finire il costruttore e costruire un oggetto, ma con un metodo 'isCoherent ()' che restituisce false, al fine di essere in grado di segnalare uno stato incoerente (che può essere preferibile in alcuni casi, al fine per evitare una brutale interruzione del flusso di lavoro di esecuzione a causa di un'eccezione)
Avvertenza: come affermato da EricSchaefer nel suo commento, ciò può portare una certa complessità al test unitario (un lancio può aumentare complessità ciclomatica della funzione a causa della condizione che la attiva)

Se fallisce a causa del chiamante (come un argomento null fornito dal chiamante, in cui il costruttore chiamato si aspetta un argomento non nullo), il costruttore genererà comunque un'eccezione di runtime non selezionata.

1
VonC

Generare un'eccezione durante la costruzione è un ottimo modo per rendere il codice molto più complesso. Le cose che sembrano semplici diventano improvvisamente difficili. Ad esempio, supponiamo che tu abbia uno stack. Come si fa a pop-up dello stack e si restituisce il valore più alto? Bene, se gli oggetti nello stack possono lanciare i loro costruttori (costruendo il temporaneo per tornare al chiamante), non puoi garantire che non perderai dati (decrementa il puntatore dello stack, costruisci il valore di ritorno usando il costruttore di copia del valore in pila, che lancia, e ora ha una pila che ha appena perso un oggetto)! Questo è il motivo per cui std :: stack :: pop non restituisce un valore e devi chiamare std :: stack :: top.

Questo problema è ben descritto qui , controlla l'articolo 10, scrivendo un codice sicuro per le eccezioni.

1
Don Neufeld

Il solito contratto in OO è che i metodi oggetto funzionano effettivamente.

Quindi, in qualità di correlativo, non restituire mai un oggetto zombi da un costruttore/init.

Uno zombi non funziona e potrebbero mancare componenti interni. Solo un'eccezione puntatore null in attesa di accadere.

Ho creato per la prima volta zombi nell'Obiettivo C, molti anni fa.

Come tutte le regole empiriche, esiste una "eccezione".

È del tutto possibile che un'interfaccia specifica possa avere un contratto che afferma che esiste un metodo di "inizializzazione" a cui è consentito il trono di un'eccezione. Che un oggetto che integra questa interfaccia potrebbe non rispondere correttamente a nessuna chiamata eccetto i setter di proprietà fino a quando non viene chiamata l'inizializzazione. L'ho usato per i driver di dispositivo in un OO durante il processo di avvio, ed era fattibile.

In generale, non vuoi oggetti zombi. In lingue come Smalltalk con diventa le cose diventano un po 'frizzanti, ma un uso eccessivo di diventa è anche un cattivo stile. Diventa consente a un oggetto di cambiare in un altro oggetto in situ, quindi non è necessario disporre di un involucro busta (Advanced C++) o del modello di strategia (GOF).

1
Tim Williscroft

La domanda del PO ha un tag "indipendente dalla lingua" ... non è possibile rispondere a questa domanda in modo sicuro allo stesso modo per tutte le lingue/situazioni.

La seguente gerarchia di classi dell'esempio C # genera il costruttore della classe B, saltando una chiamata immediata alla classe A di IDisposeable.Dispose all'uscita del principale using, saltando la disposizione esplicita delle risorse di classe A.

Se, ad esempio, la classe A avesse creato un Socket in fase di costruzione, collegato a una risorsa di rete, probabilmente lo sarebbe ancora dopo il blocco using (un'anomalia relativamente nascosta).

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.
    }
}
1
Ashley

Sto solo imparando l'obiettivo C, quindi non posso davvero parlare per esperienza, ma ho letto di questo nei documenti di Apple.

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

Non solo ti dirà come gestire la domanda che hai posto, ma fa anche un buon lavoro nel spiegarla.

0
Scott Swezey

Il miglior consiglio che ho visto sulle eccezioni è di lanciare un'eccezione se, e solo se, l'alternativa è il mancato rispetto di una condizione postale o il mantenimento di un invariante.

Tale consiglio sostituisce una decisione soggettiva poco chiara (è una buona idea) con una domanda tecnica e precisa basata su decisioni di progettazione (invarianti e condizioni post) che avresti già dovuto prendere.

I costruttori sono solo un caso particolare, ma non speciale, per quel consiglio. Quindi la domanda diventa: quali invarianti dovrebbe avere una classe? I sostenitori di un metodo di inizializzazione separato, da chiamare dopo la costruzione, stanno suggerendo che la classe ha due o più modalità operativa, con una modalità non già dopo la costruzione e almeno una - pronto modalità, inserita dopo l'inizializzazione. Questa è un'ulteriore complicazione, ma accettabile se la classe ha comunque più modalità operative. È difficile capire come valga la pena questa complicazione se la classe non avesse altrimenti modalità operative.

Notare che l'invio della configurazione in un metodo di inizializzazione separato non consente di evitare eccezioni. Le eccezioni che il costruttore potrebbe aver lanciato ora verranno generate dal metodo di inizializzazione. Tutti i metodi utili della tua classe dovranno generare eccezioni se vengono chiamati per un oggetto non inizializzato.

Si noti inoltre che evitare la possibilità che vengano generate eccezioni dal costruttore è problematico e in molti casi impossibile in molte librerie standard. Questo perché i progettisti di quelle biblioteche ritengono che gettare eccezioni dai costruttori sia una buona idea. In particolare, qualsiasi operazione che tenti di acquisire una risorsa non condivisibile o finita (come l'allocazione della memoria) può non riuscire e tale errore è generalmente indicato in OO linguaggi e librerie generando un'eccezione.

0
Raedwald

Parlando rigorosamente da un Java di Java, ogni volta che si inizializza un costruttore con valori illegali, si dovrebbe generare un'eccezione. In questo modo non viene costruito in cattivo stato.

0
scubabbl

Per me è una decisione di design un po 'filosofica.

È molto bello avere istanze valide fintanto che esistono, dal momento in poi. Per molti casi non banali ciò potrebbe richiedere il rilascio di eccezioni dal ctor se non è possibile effettuare un'allocazione di memoria/risorsa.

Alcuni altri approcci sono il metodo init () che viene fornito con alcuni problemi propri. Uno dei quali è garantire che init () venga effettivamente chiamato.

Una variante sta usando un approccio pigro per chiamare automaticamente init () la prima volta che un accessor/mutatore viene chiamato, ma ciò richiede che qualsiasi potenziale chiamante debba preoccuparsi della validità dell'oggetto. (Al contrario di "esiste, quindi è una filosofia valida").

Ho visto vari modelli di progettazione proposti per affrontare anche questo problema. Come essere in grado di creare un oggetto iniziale tramite ctor, ma dover chiamare init () per mettere le mani su un oggetto contenuto e inizializzato con accessori/mutatori.

Ogni approccio ha i suoi alti e bassi; Ho usato tutto questo con successo. Se non crei oggetti pronti per l'uso dal momento in cui sono stati creati, ti consiglio una forte dose di affermazioni o eccezioni per assicurarmi che gli utenti non interagiscano prima di init ().

Addendum

Ho scritto dal punto di vista dei programmatori C++. Presumo anche che tu stia usando correttamente il linguaggio RAII per gestire le risorse che vengono rilasciate quando vengono generate eccezioni.

0
nsanders

Utilizzando fabbriche o metodi di fabbrica per tutta la creazione di oggetti, puoi evitare oggetti non validi senza generare eccezioni dai costruttori. Il metodo di creazione dovrebbe restituire l'oggetto richiesto se è in grado di crearne uno, oppure null in caso contrario. Perdi un po 'di flessibilità nella gestione degli errori di costruzione nell'utente di una classe, perché restituire null non ti dice cosa è andato storto nella creazione dell'oggetto. Ma evita anche di aggiungere la complessità di più gestori di eccezioni ogni volta che richiedi un oggetto e il rischio di rilevare eccezioni che non dovresti gestire.

0
Tegan Mulholland