it-swarm.it

Il costruttore non dovrebbe generalmente chiamare metodi

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

esempio (nel mio arrugginito C++)

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 vostro 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 programmando in Python.

12
Stefano Borini

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 implementazione, si tratterà di una violazione di accesso.

Un costruttore può chiamare funzioni non virtuali.

Se la tua lingua è Java dove le funzioni sono generalmente virtuali per impostazione predefinita, è logico che tu debba fare 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 si hanno più costruttori che vogliono chiamare un metodo "init" comune.

Nota che i distruttori avranno lo stesso problema con i metodi virtuali, quindi non puoi avere un metodo di "pulizia" virtuale che si trova al di fuori del tuo distruttore e ti aspetti che venga chiamato dal distruttore di classe base.

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

A questo proposito, C # sembra gestire correttamente la pulizia.

(Notare che sebbene Java e C # hanno la garbage collection, che gestisce solo l'allocazione della memoria. Esiste un altro clean-up che il distruttore deve fare non rilasciando memoria).

26
CashCow

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

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

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. Pertanto, se tale metodo utilizza campi del tipo derivato, tali campi potrebbero non essere ancora inizializzati correttamente in quel momento. Questo problema è discusso in dettaglio in Effecive Java 2nd Edition , Item 17: Progetta e documento per ereditarietà o altrimenti proibiscilo.

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

18
Péter Török

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

Ecco un articolo che potrebbe aiutare a soddisfare il tuo appello all'autorità: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

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

Ciò è legato alla facilità con cui è possibile testare il codice. I motivi includono:

  1. I test unitari implicano molta creazione e distruzione, quindi la costruzione dovrebbe essere rapida.

  2. A seconda di ciò che fanno questi metodi, può essere difficile testare unità di codice discrete senza fare affidamento su alcune condizioni preliminari (potenzialmente non verificabili) impostate nel costruttore (ad es. Ottenere informazioni da una rete).

7
Paul Butcher

Filosoficamente, lo scopo del costruttore è quello di trasformare un pezzo grezzo di memoria in un'istanza. Mentre il costruttore viene eseguito, l'oggetto non esiste ancora, quindi chiamare i suoi metodi è una cattiva idea. Potresti non sapere cosa fanno internamente dopo tutto, e possono giustamente considerare l'oggetto almeno esistere (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 a tali metodi che inizializzano i membri della classe.

3
user17621

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

In lingue con sane OOP, che imposta correttamente il puntatore vtable dall'inizio, questo problema non esiste.

2
Mason Wheeler

Esistono 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 state inizializzate
  • chiamare un metodo pubblico (che dovrebbe imporre gli invarianti di classe), poiché l'oggetto non è ancora necessariamente completo (e quindi il suo invariante potrebbe non essere valido)

Non c'è nulla di sbagliato nel chiamare una funzione di supporto, purché non rientri nei due casi precedenti.

2
Matthieu M.

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

1
Jörg W Mittag

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

In C++ e Delphi, se dovessi fornire 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.

Per quanto riguarda i metodi "getter" e "setter" delle proprietà, di solito uso variabili private/protette per memorizzare il loro stato, oltre ai metodi "getter" e "setter".

Nel costruttore assegno valori "predefiniti" ai campi dello stato delle proprietà, SENZA chiamando gli "accessori".

0
umlcat