Possiamo vivere senza costruttori?

25

Diciamo che in qualche ragione tutti gli oggetti sono creati in questo modo $ obj = CLASS :: getInstance (). Quindi iniettiamo le dipendenze usando i setter ed eseguiamo l'inizializzazione iniziale usando $ obj- > initInstance (); Ci sono dei problemi o situazioni reali, che non possono essere risolti, se non useremo i costruttori?

P.S. la ragione per creare oggetti in questo modo è che possiamo sostituire la classe all'interno di getInstance () in base ad alcune regole.

Sto lavorando in PHP, se questo importa

    
posta Axel Foley 13.01.2014 - 15:19
fonte

10 risposte

44

Direi che questo ostacola pesantemente il tuo spazio di progettazione.

I costruttori sono un ottimo posto per inizializzare e validare i parametri passati. Se non puoi più usarli per questo, allora l'inizializzazione, la gestione dello stato (o addirittura la negazione del costruttore di oggetti "corrotti") diventa molto più difficile e parzialmente impossibile .

Ad esempio, se ogni Foo oggetto ha bisogno di un Frobnicator , allora potrebbe verificare nel suo costruttore se Frobnicator non è nullo. Se togli il costruttore, diventa più difficile controllarlo. Controlli in ogni punto in cui verrebbe usato? In un metodo init() (esternalizzando in modo efficace il metodo del costruttore)? Non controllarlo mai e sperare per il meglio?

Anche se potrebbe probabilmente implementare ancora tutto (dopo tutto, sei ancora pronto), alcune cose saranno molto più difficili da fare.

Personalmente suggerirei di esaminare Iniezione delle dipendenze / Inversion of Control . Queste tecniche consentono anche il passaggio a classi di implementazione concreta, ma non impediscono la scrittura / l'utilizzo di costruttori.

    
risposta data 13.01.2014 - 15:26
fonte
26

2 vantaggi per i costruttori:

I costruttori consentono di eseguire le fasi di costruzione di un oggetto in modo atomico.

Potrei evitare un costruttore e usare setters per tutto, ma per quanto riguarda le proprietà obbligatorie suggerite da Joachim Sauer? Con un costruttore, un oggetto possiede la propria logica di costruzione in modo da garantire che non ci siano istanze non valide di tale classe .

Se la creazione di un'istanza di Foo richiede l'impostazione di 3 proprietà, il costruttore potrebbe prendere un riferimento a tutti e 3, convalidarli e generare un'eccezione se non sono validi.

Encapsulation

Facendo affidamento unicamente sui setter, l'onere è sul consumatore di un oggetto per costruirlo correttamente. Potrebbero esserci diverse combinazioni di proprietà che sono valide.

Ad esempio, ogni istanza di Foo richiede un'istanza di Bar come proprietà bar o un'istanza di BarFinder come proprietà barFinder . Può usare uno dei due. Puoi creare un costruttore per ogni set di parametri valido e applicare le convenzioni in questo modo.

La logica e la semantica per gli oggetti vivono all'interno dell'oggetto stesso. È un buon incapsulamento.

    
risposta data 13.01.2014 - 16:45
fonte
15

Sì, puoi vivere senza costruttori.

Certo, si potrebbe finire con un sacco di codice duplicato della piastra della caldaia. E se la tua applicazione è di qualsiasi dimensione, probabilmente passerai molto tempo a cercare di individuare l'origine di un problema quando quel codice di codice non viene utilizzato in modo coerente nell'intera applicazione.

Ma no, non hai assolutamente bisogno dei tuoi costruttori. Naturalmente, non hai assolutamente bisogno di classi e oggetti.

Ora se il tuo obiettivo è quello di utilizzare una sorta di modello di fabbrica per la creazione dell'oggetto, che non si escludono a vicenda per l'utilizzo di costruttori durante l'inizializzazione degli oggetti.

    
risposta data 13.01.2014 - 17:17
fonte
9

Il vantaggio dell'utilizzo dei costruttori è che rendono più semplice garantire che non si abbia mai un oggetto non valido.

Un costruttore ti dà l'opportunità di impostare tutte le variabili membro dell'oggetto su uno stato valido. Quando poi assicuri che nessun metodo mutatore può cambiare l'oggetto in uno stato non valido, non avrai mai un oggetto non valido, che ti farà risparmiare da molti bug.

Ma quando un nuovo oggetto viene creato in uno stato non valido e devi chiamare alcuni setter per metterlo in uno stato valido in cui può essere usato, stai rischiando che un consumatore della classe si dimentichi di chiamare questi setter o chiamate in modo errato e si finisce con un oggetto non valido.

Una soluzione alternativa potrebbe essere quella di creare oggetti solo attraverso un metodo di fabbrica che verifica la validità di tutti gli oggetti creati prima di restituirli al chiamante.

    
risposta data 13.01.2014 - 16:36
fonte
3

$obj = CLASS::getInstance(). Then we inject dependencies using setters and perform starting initialization using $obj->initInstance();

Penso che tu stia rendendo questo più difficile di quanto dovrebbe essere. Siamo in grado di iniettare le dipendenze nel costrut- tore, e se ne hai molte, basta usare una struttura simile a un dizionario per poter specificare quali usare:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

E all'interno del costruttore, puoi garantire la coerenza in questo modo:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args può quindi essere utilizzato per impostare i membri privati secondo necessità.

Se fatto interamente all'interno del costruttore in questo modo, non ci sarà mai uno stato intermedio in cui $obj esiste ma non è inizializzato, come ci sarebbe nel sistema descritto nella domanda. È meglio evitare tali stati intermedi, perché non è possibile garantire che l'oggetto venga sempre utilizzato correttamente.

    
risposta data 13.01.2014 - 19:10
fonte
2

In realtà stavo pensando a cose simili.

La domanda che ho posto era "Che cosa fa il costruttore, ed è possibile farlo in modo diverso?" Ho raggiunto queste conclusioni:

  • Assicura che alcune proprietà siano inizializzate. Accettandoli come parametri e impostandoli. Ma questo potrebbe essere facilmente applicato dal compilatore. Annotando semplicemente i campi o le proprietà come "obbligatori", il compilatore controllerebbe durante la creazione dell'istanza se tutto è impostato correttamente. La chiamata per creare l'istanza sarebbe probabilmente la stessa, non ci sarebbe alcun metodo di costruzione.

  • Garantisce che le proprietà siano valide. Questo potrebbe essere facilmente raggiunto affermando la condizione. Ancora una volta annoteresti le proprietà con le condizioni corrette. Alcune lingue lo fanno già.

  • Qualche logica di costruzione più complessa. I modelli moderni non consigliano di farlo in costruttore, ma propongono di usare metodi o classi di fabbrica specializzati. Quindi l'uso dei costruttori in questo caso è minimo.

Quindi per rispondere alla tua domanda: Sì, credo che sia possibile. Ma richiederebbe grandi cambiamenti nella progettazione della lingua.

E ho appena notato che la mia risposta è piuttosto OT.

    
risposta data 13.01.2014 - 19:30
fonte
2

Sì, puoi fare quasi tutto senza usare costruttori, ma questo è chiaramente uno spreco di benefici dei linguaggi di programmazione orientata agli oggetti.

Nelle lingue moderne (parlerò qui di C # in cui programma) puoi limitare parti di codice che possono essere eseguite solo in un costruttore. Grazie a cui puoi evitare errori goffi. Uno di questi è il modificatore readonly :

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Poiché consigliato da Joachim Sauer in precedenza anziché utilizzare Factory design patter leggeva su Dependency Injection . Consiglierei di leggere Iniezione delle dipendenze in .NET di Mark Seemann .

    
risposta data 14.01.2014 - 10:28
fonte
1

Crea un'istanza di un oggetto con un tipo in base ai requisiti che è assolutamente possibile. Potrebbe essere l'oggetto stesso, utilizzando le variabili globali del sistema per restituire un tipo specifico.

Tuttavia, avere una classe nel codice che può essere "Tutto" è il concetto di tipo dinamico . Personalmente ritengo che questo approccio crei inconsistenze nel codice, rendano complessi i test * e "il futuro diventi incerto" rispetto al flusso del lavoro proposto.

* Mi riferisco al fatto che i test devono considerare prima il tipo, in secondo luogo il risultato che si desidera ottenere. Quindi stai creando un test nidificato di grandi dimensioni.

    
risposta data 13.01.2014 - 19:30
fonte
1

Per bilanciare alcune delle altre risposte, sostenendo:

A constructor gives you the opportunity to set all member variables of the object to a valid state... you will never have an invalid object, which will save you from a lot of bugs.

e

With a constructor, an object owns its own construction logic so as to ensure that there are no invalid instances of such class.

Tali dichiarazioni a volte implicano l'ipotesi che:

If an class has a constructor that, by the time it exits, has put the object into a valid state, and none of the class's methods mutate that state to make it invalid, then it is impossible for code outside the class to detect an object of that class in an invalid state.

Ma non è del tutto vero. La maggior parte delle lingue non ha una regola contro un costruttore che passa this (o self o qualunque sia la lingua che lo chiama) a un codice esterno. Tale costruttore si attiene completamente alla regola sopra indicata, e tuttavia rischia di esporre oggetti semi-costruiti a codice esterno. È un punto secondario ma facilmente trascurato.

    
risposta data 14.01.2014 - 12:11
fonte
0

Questo è un po 'aneddotico, ma di solito riservo ai costruttori lo stato essenziale affinché l'oggetto sia completo e utilizzabile. Escludendo ogni setter-injection, una volta eseguito il costruttore, il mio oggetto dovrebbe essere in grado di eseguire le attività richieste.

Tutto ciò che può essere differito, lascio il costruttore (preparando i valori di output, ecc.). Con questo approccio, sento di non usare costruttori per qualcosa, ma l'iniezione di dipendenza ha senso.

Questo ha anche il vantaggio di cablare il tuo processo di progettazione mentale per non fare nulla in anticipo. Non inizializzerai o eseguirai una logica che potrebbe non essere mai utilizzata perché nella migliore delle ipotesi tutto ciò che fai è una configurazione di base per il lavoro da svolgere.

    
risposta data 14.01.2014 - 15:40
fonte

Leggi altre domande sui tag