Start Method vs. Impostazione di tutto nel costruttore

3

Il framework interno utilizzato dalla mia azienda ha un oggetto piuttosto importante che ha un modello di istanziazione come membro di una classe con un costruttore senza argomenti ma per essere utilizzabile è necessario chiamare un metodo di avvio. Capisco che i costruttori siano in genere minimalisti ma allo stesso tempo perché siano in grado di costruire un oggetto inutilizzabile che devi semplicemente chiamare su start in ogni caso?

Fondamentalmente questo

public class Someclass{
    FrameworkThingy thingy = new FrameworkThingy();

    //Framework auto runs this method as part of Spring beans
    public void startUpMethod() {
        thingy.start();
        // Why not just do the start stuff in the constructor?
    }
}
    
posta ford prefect 20.10.2016 - 16:41
fonte

5 risposte

8

Quando ho visto questo modello in passato, è sempre stato per uno dei seguenti motivi:

  1. L'avvio della classe è un'operazione costosa. Lasciare la logica di avvio nel costruttore rallenterebbe il bootstrap / avvio delle applicazioni.
  2. Problemi di tempistica. Potrebbe essere che la tua classe non dovrebbe mai essere avviata prima di un altro evento nella tua applicazione. Pertanto, invece di lasciare la responsabilità di Start alla classe stessa, potrebbe essere più sensato affidare tale responsabilità a una classe coordinatrice di tempistiche.
  3. L'interruzione e Il riavvio sono anch'essi operazioni legittime e vengono sempre invocati in modo esplicito. In questo caso, fare in modo che Start venga invocato implicitamente nel costruttore interrompe il pattern e rende la semantica della classe un po 'confusa.

Senza ulteriori dettagli sulla tua situazione, non posso davvero darti una risposta più specifica.

    
risposta data 20.10.2016 - 17:21
fonte
3

Come molte domande sullo sviluppo del software, la risposta è "dipende".

Alcuni degli articoli hanno altre domande collegate nelle risposte a questo difensore per costruttori "leggeri" che evitano di fare "lavori reali". Tuttavia, ci sono molte circostanze in cui è opportuno inizializzare e iniziare a lavorare nel costruttore.

Di solito, è appropriato quando l'istanziazione è economica, quando l'istanziazione ha essenzialmente un esito concettuale positivo, e dove non c'è nulla di valido o significativo da fare tra la costruzione e un inizio ipotetico.

Per iniziare, caratterizziamo l'idea di un metodo Start (). In molte situazioni sarebbe invece chiamato Init () o Open () o simili. Probabilmente comprenderebbe cose come l'apertura o la creazione di file, l'apertura di porte o connessioni di rete, la connessione a un database, l'accesso all'hardware, l'avvio di nuovi thread, ecc. Quali sono alcune situazioni in cui avrebbe senso creare un oggetto e iniziare a eseguire tali operazioni correttamente via?

Prendi l'esempio di qualcosa che riguarda i file sul file system. Se prendiamo la posizione che dovremmo svolgere un lavoro minimo nella funzione di costruzione, un utilizzo tipico è l'istanziazione della nostra classe filewriter, quindi chiamata in seguito Open () o simile, quindi in seguito iniziare a fornire i dati in qualche modo. Dal momento che vogliamo che il nostro oggetto sia sempre in uno stato valido, dovremmo verificare che possiamo accedere al file al momento dell'istanza. Se non è possibile accedere al file, è necessario generare un'eccezione durante la costruzione. Se tutto è a posto, possiamo in seguito chiamare Open () e andare al lavoro. Ma aspetta, la vita non è mai così semplice. Abbiamo creato una condizione di competizione, poiché è possibile che tra l'istanziazione e Open (), il file sia diventato inaccessibile. Quindi, avremmo bisogno di racchiudere Open () in un altro try / catch per gestire tale scenario. Come è meglio l'istanziazione e l'apertura in un'unica operazione? Direi che in questo scenario il metodo addizionale è peggio che fare il lavoro nel costruttore.

Le procedure di istanziazione ancora più complesse possono essere eseguite con paradigma in un modo facile di debug e di test, e l'iniezione di dipendenza è spesso il mezzo per farlo. Un'interfaccia database fornita come un'istanza DatabaseConnection è un esempio comune che potresti aver visto in natura. Istanziati, gli oggetti validi vengono progressivamente iniettati in oggetti che li utilizzano, garantendo uno stato valido lungo il percorso.

Alcuni commenti e risposte hanno affermato che a volte, vuoi istanziare il tuo oggetto e poi farlo iniziare il suo lavoro, che per me ha poco senso per gli oggetti economici. È abbastanza comune affermare che le tue variabili sono tanto vicine quanto quando le userai, perché la regola dovrebbe essere diversa con gli oggetti? E se li hai separati, quale valore ti stai fornendo tenendoli separati? Se stai aprendo una porta di rete, nel momento in cui sei pronto, hai tutte le informazioni necessarie per farlo, quindi qual è la vera differenza tra una classe che ti fa chiamare un metodo aggiuntivo?

Facendo il lavoro Start () nel costruttore, si è in grado di garantire che ci sia un unico modo per usare una classe, che il modo in cui uno sia il modo giusto e che il compilatore lo imponga per te. Avendo metodi di avvio separati, si ha un accoppiamento temporale che elimina gli errori di programmazione del tempo di compilazione per gli errori di runtime. In un certo senso, iniziando dal costruttore, raccogliamo alcuni dei benefici concettuali di oggetti immutabili, ma per oggetti che fanno cose.

Ora, come ho detto, a volte la risposta è sì, a volte è no. A volte ha senso avere un metodo Start () esposto. A volte ci si interfaccia con un altro sistema in cui RAII e OOP e paradigmi simili non esistono, e non ci sono eccezioni, ecc. E non si ha scelta. A volte l'istanziazione è molto costosa. In questi casi e altri ha senso avere metodi di avvio separati. Solo tu puoi decidere cosa è appropriato.

    
risposta data 21.10.2016 - 05:34
fonte
2

Perché non è quello che servono i costruttori. I costruttori sono per la costruzione. Non mi aspetto o non voglio che gli oggetti facciano altro che essere pronti per essere usati quando li costruisco. Se iniziano a fare qualcosa che non ho chiesto loro di fare, mi sorprendi, l'utente dell'oggetto.

Separare l'uso dalla costruzione è abbastanza difficile senza occuparsi di oggetti che si rifiutano di esistere a meno che non possano fare qualcosa per primi. Un costruttore dovrebbe ottenere l'oggetto in uno stato utilizzabile. È tutto. Qualsiasi altro è confusione.

    
risposta data 20.10.2016 - 18:32
fonte
2

Poiché Misko dice che dovresti evitare fare lavori nel costruttore .

Mi stai costringendo a pensare come te

Non puoi dire il futuro. Lo so perché non posso. Come verrà utilizzato il tuo oggetto? Puoi presumere che sarà usato in questo modo, ogni volta? Forse qualcuno troverà un uso per questo in fondo alla strada che non avevi immaginato, ma è pratico. E richiede anche che non chiami il metodo start . Poiché hai spostato quella logica nel costruttore sono solo sfortunati e un'opportunità per il riutilizzo del codice è perso. Perché volevi chiamare un metodo start nel costruttore.

Posso fidarmi di una nuova istanza del tuo oggetto?

L'unica costante nello sviluppo del software è che le cose cambiano. Voglio una nuova istanza del tuo oggetto, e lo voglio subito. Ma tu chiami un metodo di avvio nel costruttore. Poi arriva qualcuno e aggiunge alcune funzionalità al metodo start, e ora l'istanza che volevo è completamente diversa, anche se non sono state introdotte nuove dipendenze . Non ho modificato nessuno dei miei codici, ma ora non funziona e è tutta colpa tua . Perché volevi chiamare un metodo start nel costruttore.

Sono un programmatore e questo significa che sono pigro

Ho 28 cose da fare al lavoro oggi. So che devo andare a quell'incontro alle 14:00. La sala riunioni è dietro l'angolo e in fondo al corridoio, quindi penso che arriverò in orario se lascerò la mia scrivania alle 13:58. So che se me ne vado prima rimarrò lì seduto a giocherellare con i miei pollici mentre aspetto che arrivino altre persone. Questo è inefficiente, che è la ragione principale della mia pigrizia. Anche se sto navigando su programmers.stackexchange.com tra 1:52 pm e 1:58 pm, questo è un molto miglior uso del mio tempo rispetto al semplice stare seduti in una sala riunioni contando i pannelli del controsoffitto. Inoltre, potrei ricevere un'e-mail alle 13:57 dicendo che l'incontro è stato annullato, ma se avessi lasciato la mia scrivania alle 13:55 non l'avrei capito, e probabilmente sarebbe stato intorno alle 2:05 pm prima di iniziare a ricevere un po 'ansioso di essere l'unico in circolazione, nel senso che, poiché volevo diventare un desideroso go-getter, in realtà ho perso un extra 10 minuti interi piastrelle. Questo è esattamente quello che è successo l'ultima volta. Non ce ne sono molti, quindi li ho contati due volte, giusto per essere sicuro di aver avuto ragione (ero).

Il punto è che, anche se ora ho bisogno di una nuova istanza del tuo oggetto, lo preferirei molto se tu fossi fammi iniziare la cosa dannata solo quando sono bravo e pronto e sicuro che voglio che inizi effettivamente. Se nel costruttore si chiama automaticamente start , mi stai costringendo a costruire quell'oggetto in un punto particolare nel momento in cui tu vuoi che lo faccia. Cosa succede se voglio costruirlo prima per qualche motivo, ma a quel punto non sono ancora pronto per start ? Non posso, perché volevi chiamare un metodo start nel costruttore.

Test

Questa penso sia la ragione principale. È necessario prendere in giro un oggetto da testare, ma le parti interne di tale oggetto sono modificate (il lavoro è fatto) durante la fase di costruzione. Con solo un oggetto mock, tuttavia, quel codice non ha la possibilità di essere eseguito, quindi non devi solo prendere in giro quell'oggetto ogni volta che devi testare qualcosa che lo usa, ma anche modificare l'oggetto fittizio nello stesso modo del tuo start metodo ogni singola volta . Oh, Joe ha aggiornato il metodo start , quindi ora significa Ho 57 test di cui ho bisogno aggiornare prima che passi la build. Questo è molto duro e probabilmente non produrrà alcun test scritto su qualcosa che si basa sull'oggetto con il costruttore pesante.

Perché tu vuoi chiamare un metodo start nel costruttore.

    
risposta data 20.10.2016 - 22:56
fonte
1

TL; DR

Nelle app mobili, alcuni oggetti sono creati una volta, ma avviati più volte - quindi il posizionamento del codice deve essere fatto con attenzione per evitare vari effetti indesiderati che portano a cattivi UX.

Avendo esperienza con lo sviluppo mobile , incontro quotidianamente situazioni 1 in cui determinati oggetti devono passare attraverso un set di inizializzazione, o ciclo di vita metodi prima che siano veramente utilizzabili.

Per una persona che crea la sua prima app, potrebbe sembrare del tutto superfluo separare onCreate() da onStart() e forse anche da onResume() . Perchè è questo? Probabilmente perché il caso d'uso previsto è qualcosa come una calcolatrice sul desktop - "apri l'app, fai le cose che ti servono, fai clic sul pulsante di uscita grande, riavvia l'app in un secondo momento, se necessario". Sicuramente, questo può funzionare bene in alcuni casi, ma cosa succede quando devi chiamare mentre lavori con l'app, o Dio non voglia, provare a caricare una foto?

Se tu, il principiante, hai deciso di mettere tutto in onCreate() , quando l'app è ripresa potrebbe non rispondere o avere uno stato imprevisto (animazioni in pausa?). Se metti tutto in onResume() , potresti riscontrare alcune perdite di memoria perché finirai per creare un'istanza degli oggetti più di una volta.

Su piattaforme con risorse relativamente limitate, lo sviluppatore deve essere molto attento a come e quando usarle, perché ogni scelta si riduce all'esperienza utente - l'app è reattiva? Consuma molto potere? Richiede all'utente di ripetere le stesse azioni più e più volte?

Per concludere: suddividere il codice di inizializzazione in più fasi (o metodi) può migliorare le prestazioni (o almeno il aspetto delle prestazioni, non dover eseguire codice una quantità inutile di volte) e esperienza utente complessiva.

1 : gli esempi più chiari sono Android Activity riassunto o iOS UIViewController .

    
risposta data 21.10.2016 - 09:45
fonte

Leggi altre domande sui tag