Stile di codifica OOP: inizializzare tutto sul costruttore?


14

Mi considero ancora un programmatore apprendista, quindi cerco sempre di imparare un modo "migliore" per la programmazione tipica. Oggi, il mio collega ha sostenuto che il mio stile di programmazione fa un lavoro superfluo e voglio sentire le opinioni degli altri. In genere, quando progetto una classe in linguaggio OOP (solitamente C ++ o Python), separerei l'inizializzazione in due parti diverse:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(o, equivalente di Python)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

Qual è la tua opinione su questo approccio? Devo astenermi dal dividere il processo di inizializzazione? La domanda non si limita solo a C ++ e Python e sono apprezzate anche le risposte per altre lingue.





Perché di solito lo fai? Abitudine? Ti è mai stato dato un motivo per farlo?
JeffO,

@JeffO Ho avuto questa abitudine quando stavo lavorando per creare GUI con la libreria MFC. La maggior parte delle classi correlate all'interfaccia utente, come CApp, CWindow, CDlg e così via, hanno una funzione OnInit () che puoi sovrascrivere, che risponde ai loro messaggi di risposta.
Caladbolgll,

Risposte:


28

Sebbene a volte sia problematico, ci sono molti vantaggi nell'inizializzare tutto nel costruttore:

  1. Se si verifica un errore, si verifica il più rapidamente possibile ed è più facile da diagnosticare. Ad esempio, se null è un valore argomento non valido, testare e fallire nel costruttore.
  2. L'oggetto è sempre in uno stato valido. Un collega non può sbagliare e dimenticare di chiamare initMyClass1()perché non c'è . "I componenti più economici, veloci e affidabili sono quelli che non esistono."
  3. Se ha senso, l'oggetto può essere reso immutabile, il che ha molti vantaggi.

2

Pensa all'astrazione che stai fornendo ai tuoi utenti.

Perché dividere qualcosa che potrebbe essere fatto in un colpo in due?

L'inizializzazione extra è solo qualcosa in più per i programmatori che usano l'API da ricordare e fornisce di più per sbagliare se non lo fanno nel modo giusto, ma per quale valore hanno questo onere aggiuntivo?

Vuoi fornire astrazioni semplici, facili da usare, difficili da sbagliare. La programmazione è abbastanza difficile senza cose gratuite da ricordare / cerchi da saltare. Desideri che i tuoi utenti API (anche se stai usando solo la tua API) cadano nella fossa del successo .


1

Inizializza tutto tranne l'area dei big data. Gli strumenti di analisi statica contrassegneranno i campi non inizializzati nel costruttore. Tuttavia, il modo più produttivo / sicuro è avere tutte le variabili membro con costruttori predefiniti e inizializzare esplicitamente solo quelle che richiedono l'inizializzazione non predefinita.


0

Ci sono casi in cui l'oggetto ha molte inizializzazioni che possono essere divise in due categorie:

  1. Attributi immutabili o che non devono essere ripristinati.

  2. Attributi che potrebbero aver bisogno di ripristinare i valori originali (o valori templatised) in base a una condizione dopo aver adempiuto al loro lavoro, una sorta di soft reset. ad es. connessioni in un pool di connessioni.

Qui la seconda parte dell'inizializzazione viene mantenuta in una funzione separata, ad esempio InitialiseObject (), può essere chiamata nel ctor.

La stessa funzione può essere richiamata in seguito se è necessario un ripristino software, senza dover scartare e ricreare l'oggetto.


0

Come altri hanno già detto, è generalmente una buona idea inizializzare nel costruttore.

Vi sono, tuttavia, ragioni che non possono essere applicabili in casi specifici.

Gestione degli errori

In molte lingue, l'unico modo per segnalare un errore in un costruttore è sollevare un'eccezione.

Se la tua inizializzazione ha una ragionevole possibilità di generare un errore, ad es. Coinvolge IO o i suoi parametri potrebbero essere input dell'utente, quindi l'unico meccanismo a tua disposizione è sollevare un'eccezione. In alcuni casi, questo potrebbe non essere quello desiderato e potrebbe essere più sensato separare il codice soggetto a errori in una funzione di inizializzazione separata.

Probabilmente l'esempio più comune di ciò è in C ++ se lo standard di progetto / organizzazione deve disattivare le eccezioni.

Macchina a stati

Questo è il caso in cui si sta modellando un oggetto con transizioni di stato esplicite. Ad esempio, un file o un socket che può essere aperto e chiuso.

In questo caso, è comune per la costruzione (e la cancellazione) dell'oggetto gestire solo gli attributi orientati alla memoria (nome file, porta ecc.). Ci saranno quindi funzioni per gestire in modo specifico le transizioni di stato, ad es. Apertura, chiusura, che sono efficacemente inizializzazione e funzioni di abbattimento.

I vantaggi sono nella gestione degli errori, come sopra, ma potrebbe anche esserci un caso per separare la costruzione dall'inizializzazione (supponiamo che tu costruisca un vettore di file e li apra in modo asincrono).

Lo svantaggio, come altri hanno già detto, è che ora poni l'onere della gestione statale sull'utente delle tue classi. Se riuscissi a gestirlo solo con la costruzione, potresti, per esempio, usare RAII per farlo automagicamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.