Domanda

Quando è giusto che un costruttore lanci un'eccezione?(Oppure nel caso dell'Obiettivo C:quando è giusto che un init'er restituisca nil?)

Mi sembra che un costruttore dovrebbe fallire - e quindi rifiutarsi di creare un oggetto - se l'oggetto non è completo.Cioè, 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?

È stato utile?

Soluzione

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

Un gruppo è favorevole alla costruzione in due fasi.Il costruttore porta semplicemente l'oggetto in uno stato dormiente in cui rifiuta di svolgere qualsiasi lavoro.C'è una funzione aggiuntiva che esegue l'inizializzazione vera e propria.

Non ho mai capito il ragionamento dietro questo approccio.Sono fermamente nel gruppo che sostiene la costruzione in una fase, dove l'oggetto è completamente inizializzato e utilizzabile dopo la costruzione.

I costruttori a una fase dovrebbero lanciare un'eccezione se non riescono a inizializzare completamente l'oggetto.Se l'oggetto non può essere inizializzato, non gli deve essere consentito di esistere, quindi il costruttore deve lanciare un'eccezione.

Altri suggerimenti

Eric Lippert dice ci sono 4 tipi di eccezioni.

  • Le eccezioni fatali non sono colpa tua, non puoi prevenirle e non puoi eliminarle in modo sensato.
  • Le eccezioni stupide sono colpa tua, avresti potuto prevenirle e quindi sono bug nel tuo codice.
  • Le eccezioni fastidiose sono il risultato di decisioni progettuali sfortunate.Le eccezioni fastidiose vengono lanciate in circostanze del tutto non eccezionali e pertanto devono essere individuate e gestite in ogni momento.
  • Infine, le eccezioni esogene sembrano essere in qualche modo eccezioni fastidiose, tranne per il fatto che non sono il risultato di scelte progettuali sfortunate.Piuttosto, sono il risultato di realtà esterne disordinate che interferiscono con la logica del tuo programma, bello e nitido.

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

Eccezioni stupide non dovrebbero mai verificarsi in nessuno dei tuoi codici, quindi sono esenti.

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

Infine, dovrebbero essere evitate le 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.

C'è generalmente non si ottiene nulla separando l'inizializzazione dell'oggetto dalla costruzione.RAII è corretto, una chiamata riuscita al costruttore dovrebbe risultare in un oggetto live completamente inizializzato oppure dovrebbe fallire, e TUTTO gli errori in qualsiasi punto di qualsiasi percorso del codice dovrebbero sempre generare un'eccezione.Non ottieni nulla utilizzando un metodo init() separato tranne ulteriore complessità ad un certo livello.Il contratto ctor dovrebbe essere o restituisce un oggetto funzionale valido oppure si ripulisce e lancia.

Considera, se implementi un metodo init separato, tu Ancora devo chiamarlo.Avrà ancora il potenziale per lanciare eccezioni, dovranno ancora essere gestite e praticamente dovranno sempre essere chiamate immediatamente dopo il costruttore, tranne che ora hai 4 possibili stati dell'oggetto invece di 2 (IE, costruito, inizializzato, non inizializzato, e fallito vs semplicemente valido e inesistente).

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

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

Se è necessario convalidare qualcosa durante la costruzione, rendere privato il costruttore e definire un metodo factory statico pubblico.Il metodo può lanciare un'eccezione se qualcosa non è valido.Ma se tutto va a buon fine, chiama il costruttore, il che garantisce che non venga lanciata un'eccezione.

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

Ad esempio, se il costruttore dovrebbe allocare 1024 KB di RAM e non lo fa, dovrebbe lanciare 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 per metà inizializzati e per metà morti causano solo problemi e problemi, poiché in realtà non c'è modo per il chiamante 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.

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

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

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

Per quanto ne so, nessuno sta presentando una soluzione abbastanza ovvia che incarni il meglio sia della costruzione a uno stadio che a quella a due stadi.

Nota: Questa risposta presuppone C#, ma i principi possono essere applicati nella maggior parte dei linguaggi.

Innanzitutto, i vantaggi di entrambi:

Uno stadio

La costruzione a uno stadio ci avvantaggia impedendo che gli oggetti esistano in uno stato non valido, prevenendo 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 generino eccezioni, e talvolta è 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 convalida

La costruzione in due fasi ci avvantaggia consentendo l'esecuzione della nostra convalida all'esterno 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'è uno stato che dobbiamo monitorare e gestire per l'istanza, altrimenti lo elimineremo immediatamente dopo l'allocazione dell'heap.Si pone la domanda:Perché stiamo eseguendo un'allocazione dell'heap, e quindi una raccolta di memoria, su un oggetto che non utilizziamo nemmeno?

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

Monostadio tramite costruttore privato

Quindi, come possiamo tenere le eccezioni fuori dai nostri costruttori e impedire a noi stessi di eseguire l'allocazione dell'heap su oggetti che verranno immediatamente scartati?È piuttosto semplice:rendiamo il costruttore privato e creiamo istanze tramite un metodo statico designato per eseguire solo un'istanziazione, e quindi l'allocazione dell'heap, Dopo convalida.

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

Asincrono monostadio tramite costruttore privato

A parte i vantaggi sopra menzionati di convalida e prevenzione dell'allocazione heap, la metodologia precedente ci offre un altro vantaggio interessante:supporto asincrono.Ciò è utile quando si ha a che fare con l'autenticazione a più fasi, ad esempio quando è necessario recuperare un token di connessione prima di utilizzare l'API.In questo modo, non ti ritroverai con un client API "disconnesso" non valido e potrai invece semplicemente ricreare il client API se ricevi un errore di autorizzazione durante il tentativo 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 svantaggi 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é la deserializzazione su un oggetto senza un costruttore predefinito pubblico è, nella migliore delle ipotesi, difficile.Tuttavia, se stavi utilizzando l'oggetto come DTO, non dovresti realmente convalidare l'oggetto stesso, ma piuttosto invalidare i valori sull'oggetto mentre tenti di usarli, poiché tecnicamente i valori non sono "non validi" per quanto riguarda al DTO.

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

Vedere le sezioni delle domande frequenti su C++ 17.2 E 17.4.

In generale, ho scoperto che il codice che è più facile da trasferire e mantenere i risultati se i costruttori sono scritti in modo da non fallire 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 .

Se stai scrivendo controlli dell'interfaccia utente (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 utilizza l'inizializzazione pigra ove possibile.

Tieni presente che se lanci un'eccezione in un inizializzatore, finirai per perdere se qualche codice utilizza il file [[[MyObj alloc] init] autorelease] pattern, poiché l'eccezione salterà il rilascio automatico.

Vedi questa domanda:

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

Dovresti assolutamente lanciare un'eccezione da un costruttore se non sei in grado di creare un oggetto valido.Ciò ti consente di fornire invarianti corretti nella tua classe.

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

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

Genera un'eccezione se non sei in grado di inizializzare l'oggetto nel costruttore, un esempio sono gli argomenti illegali.

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

Lanciare un'eccezione durante la costruzione è un ottimo modo per rendere il tuo codice molto più complesso.Le cose che sembrerebbero semplici diventano improvvisamente difficili.Ad esempio, supponiamo che tu abbia uno stack.Come si fa a far apparire lo stack e restituire il valore più alto?Bene, se gli oggetti nello stack possono inserire i loro costruttori (costruendo il temporaneo da restituire al chiamante), non puoi garantire di non perdere dati (diminuisci il puntatore dello stack, costruisci il valore restituito usando il costruttore di copia del valore in pila, che lancia, e ora hai 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 codice sicuro contro le eccezioni.

Il contratto normale in OO è che i metodi degli oggetti funzionino effettivamente.

Quindi, come corollario, non restituire mai un oggetto zombie da un costruttore/init.

Uno zombie non è funzionante e potrebbero mancare componenti interni.Solo un'eccezione del puntatore nullo in attesa di verificarsi.

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

Come tutte le regole pratiche, esiste un'"eccezione".

È del tutto possibile che a interfaccia specifica può avere un contratto che afferma che esiste un metodo "inizializza" che è autorizzato a fare un'eccezione.Che un oggetto che implementa questa interfaccia potrebbe non rispondere correttamente a tutte le chiamate ad eccezione dei setter di proprietà finché non viene chiamata l'inizializzazione.L'ho usato per i driver di dispositivo in un sistema operativo OO durante il processo di avvio ed era fattibile.

In generale, non vuoi oggetti zombie.In lingue come Smalltalk con diventare le cose diventano un po' frizzanti, ma se ne fa un uso eccessivo diventare è anche di cattivo stile. Diventa consente a un oggetto di trasformarsi in un altro oggetto in situ, quindi non è necessario l'involucro di busta (Advanced C++) o il modello di strategia (GOF).

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

La funzione try block è stata progettata specificamente per supportare gli errori nell'inizializzazione dei membri 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.

Sì, se il costruttore non riesce a costruire una delle sue parti interne, può essere, per scelta, sua responsabilità lanciare (e in un certo linguaggio dichiarare) un eccezione esplicita , debitamente annotato nella documentazione del costruttore.

Questa non è l'unica opzione:Potrebbe terminare il costruttore e costruire un oggetto, ma con un metodo 'isCoherent()' che restituisca false, in modo da poter segnalare uno stato incoerente (che può essere preferibile in certi casi, per evitare una brutale interruzione del flusso di lavoro di esecuzione a causa di un'eccezione)
Avvertimento:come detto da EricSchaefer nel suo commento, ciò può portare una certa complessità al test unitario (un tiro può aumentare il complessità ciclomatica della funzione a causa della condizione che la attiva)

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

Non sono sicuro che qualsiasi risposta possa essere del tutto indipendente dalla lingua.Alcuni linguaggi gestiscono le eccezioni e la gestione della memoria in modo diverso.

Ho lavorato in precedenza con standard di codifica che richiedevano che le eccezioni non venissero mai utilizzate e solo codici di errore sugli inizializzatori, perché gli sviluppatori erano rimasti scottati dal linguaggio che gestiva male le eccezioni.Le lingue senza garbage collection gestiranno l'heap e lo stack in modo molto diverso, il che potrebbe avere importanza per 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 riguardo alle eccezioni che possono generare, in modo che i chiamanti sappiano come gestirle.

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

  • Il supporto linguistico per le eccezioni non è molto buono.
  • Hai un motivo di progettazione urgente per continuare a utilizzarlo new E delete
  • L'inizializzazione richiede un uso intensivo del processore e dovrebbe essere eseguita in modo asincrono rispetto al thread che ha creato l'oggetto.
  • Stai creando una DLL che potrebbe generare eccezioni all'esterno della sua interfaccia a un'applicazione che utilizza una lingua diversa.In questo caso potrebbe non essere tanto un problema non generare eccezioni, ma assicurarsi che vengano catturate prima dell'interfaccia pubblica.(Puoi rilevare le eccezioni C++ in C#, ma ci sono dei passaggi da eseguire.)
  • Costruttori statici (C#)

La domanda dell'OP ha un tag "indipendente dalla lingua" ...a questa domanda non è possibile rispondere con sicurezza allo stesso modo per tutte le lingue/situazioni.

La gerarchia di classi dell'esempio C# seguente inserisce il costruttore della classe B, saltando una chiamata immediata alla classe A IDisposeable.Dispose all'uscita della principale using, saltando lo smaltimento esplicito delle risorse di classe A.

Se, ad esempio, la classe A avesse creato a Socket in fase di costruzione, collegato a una risorsa di rete, probabilmente sarà ancora così dopo using blocco (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.
    }
}

Parlando strettamente dal punto di vista Java, ogni volta che si inizializza un costruttore con valori illegali, dovrebbe generare un'eccezione.In questo modo non verrà costruito in cattivo stato.

Per me è una decisione progettuale in qualche modo filosofica.

È molto bello avere istanze valide finché esistono, dal momento del ctor in poi.Per molti casi non banali ciò potrebbe richiedere il lancio di eccezioni da parte del ctor se non è possibile effettuare un'allocazione di memoria/risorse.

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

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

Ho visto anche vari modelli di progettazione proposti per affrontare 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 tutti questi con successo.Se non crei oggetti pronti all'uso dall'istante in cui vengono creati, allora consiglio una forte dose di assert o eccezioni per assicurarti che gli utenti non interagiscano prima di init().

Addendum

Ho scritto dal punto di vista dei programmatori C++.Presumo anche che tu stia utilizzando correttamente l'idioma RAII per gestire le risorse rilasciate quando vengono lanciate eccezioni.

Sto appena imparando l'Obiettivo C, quindi non posso davvero parlare per esperienza, ma ne ho letto 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 farà anche un buon lavoro nel spiegarla.

Utilizzando factory o metodi factory per tutta la creazione di oggetti, è possibile evitare oggetti non validi senza generare eccezioni dai costruttori.Il metodo di creazione dovrebbe restituire l'oggetto richiesto se è in grado di crearne uno o 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.

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

Questo consiglio sostituisce una decisione soggettiva poco chiara (è a buona idea) con una domanda tecnica precisa basata sulle decisioni progettuali (condizioni invarianti e post) che avresti già dovuto prendere.

I costruttori sono solo un caso particolare, ma non speciale, per questo consiglio.Quindi la domanda diventa: quali invarianti dovrebbe avere una classe?I sostenitori di un metodo di inizializzazione separato, da richiamare dopo la costruzione, suggeriscono che la classe ne abbia due o più modalità operativa, con un non pronto modalità dopo la costruzione e almeno uno pronto modalità, inserita dopo l'inizializzazione.Questa è una complicazione aggiuntiva, ma accettabile se la classe ha comunque più modalità operative.È difficile capire come valga la pena di fare questa complicazione se altrimenti la classe non avrebbe modalità operative.

Tieni presente che l'inserimento di set up in un metodo di inizializzazione separato non ti consente di evitare che vengano generate eccezioni.Le eccezioni che il tuo costruttore potrebbe aver lanciato verranno ora lanciate dal metodo di inizializzazione.Tutti i metodi utili della tua classe dovranno lanciare eccezioni se vengono chiamati per un oggetto non inizializzato.

Nota anche che evitare la possibilità che vengano lanciate eccezioni dal tuo costruttore è problematico, e in molti casi impossibile in molte librerie standard.Questo perché i progettisti di tali librerie ritengono che generare eccezioni dai costruttori sia una buona idea.In particolare, qualsiasi operazione che tenta di acquisire una risorsa non condivisibile o limitata (come l'allocazione della memoria) può fallire e tale errore viene generalmente indicato nei linguaggi e nelle librerie OO lanciando un'eccezione.

gli operatori non dovrebbero fare alcuna cosa "intelligente", quindi non è comunque necessario lanciare un'eccezione.Utilizzare un metodo Init() o Setup() se si desidera eseguire un'impostazione dell'oggetto più complicata.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top