Il costruttore in genere non dovrebbe chiamare metodi

11

Ho descritto a un collega perché un costruttore che chiama un metodo può essere antipattern.

esempio (nel mio C ++ arrugginito)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

Vorrei motivare meglio questo fatto attraverso il tuo contributo aggiuntivo. Se hai esempi, riferimenti a libri, pagine di blog o nomi di principi, sarebbero i benvenuti.

Modifica: sto parlando in generale, ma stiamo codificando in python.

    
posta Stefano Borini 17.02.2011 - 16:35
fonte

8 risposte

24

Non hai specificato una lingua.

In C ++ un costruttore deve fare attenzione quando chiama una funzione virtuale, in quanto la funzione effettiva che sta chiamando è l'implementazione della classe. Se si tratta di un metodo virtuale puro senza un'implementazione, questa sarà una violazione di accesso.

Un costruttore può chiamare funzioni non virtuali.

Se la tua lingua è Java, in cui le funzioni sono generalmente virtuali per impostazione predefinita, è logico che tu debba prestare molta attenzione.

C # sembra gestire la situazione come ti aspetteresti: puoi chiamare metodi virtuali nei costruttori e chiama la versione più definitiva. Quindi in C # non è un anti-pattern.

Un motivo comune per chiamare i metodi dai costruttori è che hai più costruttori che vogliono chiamare un metodo "init" comune.

Si noti che i distruttori avranno lo stesso problema con i metodi virtuali, quindi non si può avere un metodo di "pulizia" virtuale che si trova all'esterno del distruttore e si aspetta che venga chiamato dal distruttore della classe base.

Java e C # non hanno distruttori, hanno i finalizzatori. Non conosco il comportamento con Java.

C # sembra gestire correttamente il clean-up a questo proposito.

(Si noti che sebbene Java e C # dispongano della garbage collection, che gestisce solo l'allocazione della memoria.) Ci sono altri ripulimenti che il distruttore deve fare per non rilasciare memoria).

    
risposta data 17.02.2011 - 16:45
fonte
18

OK, ora che la confusione riguardante i metodi della classe rispetto ai metodi di istanza è stata chiarita, posso dare una risposta: -)

Il problema non è con la chiamata di metodi di istanza in generale da un costruttore; è con i metodi di chiamata virtuali (direttamente o indirettamente). E il motivo principale è che mentre si trova all'interno del costruttore, l'oggetto non è ancora completamente costruito . E soprattutto le sue parti di sottoclasse non sono costruite durante l'esecuzione del costruttore della classe base. Quindi il suo stato interno è incoerente in un modo dipendente dalla lingua, e questo può causare diversi sottili bug in lingue diverse.

C ++ e C # sono già stati discussi da altri. In Java, verrà chiamato il metodo virtuale del tipo più derivato, tuttavia quel tipo non è ancora inizializzato. Quindi se quel metodo sta usando qualsiasi campo dal tipo derivato, quei campi potrebbero non essere ancora inizializzati correttamente in quel momento. Questo problema è discusso in dettaglio in Effecive Java 2nd Edition , elemento 17: Progettazione e documentazione per l'ereditarietà oppure proibirlo .

Si noti che questo è un caso particolare del problema generale di riferimenti all'oggetto di pubblicazione prematuramente . I metodi di istanza hanno un parametro this implicito, ma il passaggio di this esplicitamente a un metodo può causare problemi simili. Soprattutto nei programmi concorrenti in cui se il riferimento all'oggetto viene pubblicato prematuramente su un altro thread, quel thread può già richiamare i metodi su di esso prima che il costruttore nel primo thread termini.

    
risposta data 17.02.2011 - 17:05
fonte
7

Non considererei le chiamate di metodo qui come un antipattern in sé, più un odore di codice. Se una classe fornisce un metodo reset , restituisce un oggetto al suo stato originale, quindi chiamando reset() nel costruttore è DRY. (Non sto facendo alcuna dichiarazione sui metodi di ripristino).

Ecco un articolo che potrebbe aiutarti a soddisfare il tuo appello all'autorità: link

Non si tratta di chiamare metodi, ma di costruttori che fanno troppo. IMHO, chiamare metodi in un costruttore è un odore che potrebbe indicare che un costruttore è troppo pesante.

Questo è legato alla facilità con cui testare il tuo codice. I motivi includono:

  1. I test unitari coinvolgono molte creazione e distruzione - quindi la costruzione dovrebbe essere veloce.

  2. A seconda di cosa fanno questi metodi, potrebbe rendere difficile il test unità discrete di codice senza basandosi su alcuni (potenzialmente precondizione non verificabile) impostata nel costruttore (ad esempio, ottieni informazioni da una rete).

risposta data 17.02.2011 - 17:50
fonte
3

Filosoficamente, lo scopo del costruttore è trasformare un pezzo grezzo di memoria in un'istanza. Mentre il costruttore è in esecuzione, l'oggetto non esiste ancora, quindi chiamare i suoi metodi è una cattiva idea. Potresti non sapere cosa fanno internamente dopotutto, e possono giustamente considerare l'oggetto come minimo (duh!) Quando vengono chiamati.

Tecnicamente, potrebbe non esserci nulla di sbagliato in questo, in C ++ e specialmente in Python, sta a te fare attenzione.

In pratica, dovresti limitare le chiamate solo ai metodi che inizializzano i membri della classe.

    
risposta data 18.02.2011 - 00:50
fonte
2

Non è un problema generico. È un problema in C ++, in particolare quando si utilizzano metodi ereditari e virtuali, perché la costruzione degli oggetti avviene all'indietro e i puntatori vtable vengono reimpostati con ogni livello di costruzione nella gerarchia dell'ereditarietà, quindi se si è chiamando un metodo virtuale potresti non ottenere quello che corrisponde effettivamente alla classe che stai tentando di creare, il che vanifica l'intero scopo dell'utilizzo di metodi virtuali.

Nelle lingue con supporto OOP ragionevole, che imposta il puntatore vtable correttamente dall'inizio, questo problema non esiste.

    
risposta data 17.02.2011 - 18:27
fonte
2

Ci sono due problemi con la chiamata di un metodo:

  • chiamando un metodo virtuale, che può fare qualcosa di inaspettato (C ++) o usare parti degli oggetti che non sono ancora stati inizializzati
  • che chiama un metodo pubblico (che dovrebbe applicare gli invarianti di classe), dal momento che l'oggetto non è necessariamente ancora completo (e quindi il suo invariante non può essere mantenuto)

Non c'è niente di sbagliato nel chiamare una funzione di aiuto, purché non cada nei due casi precedenti.

    
risposta data 17.02.2011 - 20:08
fonte
1

Non lo compro In un sistema orientato agli oggetti, chiamare un metodo è praticamente l'unica cosa che puoi fare. In effetti, è più o meno la definizione di "object-oriented". Quindi, se un costruttore non può chiamare alcun metodo, cosa può fare ?

    
risposta data 17.02.2011 - 21:52
fonte
0

In O.O.P. teoria, non dovrebbe importare, ma in pratica, ogni O.O.P. il linguaggio di programmazione gestisce i costruttori diversi . Non uso molto spesso metodi statici.

In C ++ e amp; Delphi, Se dovessi dare dei valori iniziali ad alcune proprietà ("membri del campo"), e il codice è molto esteso, aggiungo alcuni metodi secondari come estensione dei costruttori.

E non chiamare altri metodi che fanno cose più complesse.

Come per le proprietà "getter" & metodi "setter", di solito uso variabili private / protette per memorizzare il loro stato, oltre a "getter" & metodi "setter".

Nel costruttore assegno i valori "predefiniti" ai campi dello stato delle proprietà, WITHOUT chiamando gli "accessors".

    
risposta data 19.03.2011 - 19:42
fonte

Leggi altre domande sui tag