Possiamo vivere senza costruttori?


25

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

Ps il motivo per creare un oggetto in questo modo è che possiamo sostituire la classe all'interno di getInstance () secondo alcune regole.

Sto lavorando in PHP, se è importante


quale linguaggio di programmazione?
moscerino del

2
PHP (perché è importante?)
Axel Foley,

8
Sembra che ciò che vuoi fare possa essere realizzato usando il modello Factory.
superM

1
Proprio come una nota: la factory patern @superM menzionata è un modo per implementare Inversion of Control (Dependency Injection) è un altro.
Joachim Sauer,

Non è più o meno ciò che javascript fa con gli oggetti?
Thijser,

Risposte:


44

Direi che questo ostacola gravemente 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, l'inizializzazione, la gestione dello stato (o negare chiaramente il costruttore di oggetti "rotti") diventa molto più difficile e parzialmente impossibile.

Ad esempio, se ogni Foooggetto ha bisogno di un Frobnicatorallora potrebbe verificare nel suo costruttore se Frobnicatornon è nullo. Se porti via il costruttore, diventa più difficile controllare. Controlli in ogni punto in cui verrebbe utilizzato? In un init()metodo (esternalizzare efficacemente il metodo del costruttore)? Non controllarlo mai e sperare per il meglio?

Mentre probabilmente potresti ancora implementare tutto (dopotutto, sei ancora in fase di completamento), alcune cose saranno molto più difficili da fare.

Personalmente suggerirei di esaminare Iniezione di dipendenza / Inversione del controllo . Queste tecniche consentono anche di cambiare classi di implementazione concrete, ma non impediscono di scrivere / usare i costruttori.


Che cosa intendevi con "o negando chiaramente il costruttore di oggetti" rotti "?
Geek,

2
@Geek: un costruttore può ispezionare il suo argomento e decidere se ciò comporterebbe un oggetto funzionante (ad esempio se il tuo oggetto ha bisogno di un HttpClientallora controlla se quel parametro non è nullo). E se tali vincoli non vengono rispettati, può generare un'eccezione. Questo non è davvero possibile con l'approccio costrutti e imposta valori.
Joachim Sauer,

1
Penso che OP stesse semplicemente descrivendo l'esternazione del costruttore all'interno init(), il che è del tutto possibile, sebbene ciò imponga solo un onere di manutenzione.
Peter,

26

2 vantaggi per i costruttori:

I costruttori consentono di eseguire atomicamente le fasi di costruzione di un oggetto.

Potrei evitare un costruttore e usare setter per tutto, ma che dire delle proprietà obbligatorie come ha suggerito Joachim Sauer? Con un costruttore, un oggetto possiede la propria logica di costruzione in modo da garantire che non vi siano istanze non valide di tale classe .

Se la creazione di un'istanza di Foorichiede l'impostazione di 3 proprietà, il costruttore potrebbe prendere un riferimento a tutte e 3, convalidarle e generare un'eccezione se non sono valide.

incapsulamento

Facendo affidamento esclusivamente sui setter, l'onere del consumatore di un oggetto è di costruirlo correttamente. Potrebbero esserci diverse combinazioni di proprietà valide.

Ad esempio, ogni Fooistanza necessita di un'istanza di Baras property baro di un'istanza di BarFinderas property barFinder. Può usare uno dei due. È possibile creare un costruttore per ogni set di parametri valido e applicare le convenzioni in quel modo.

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


15

Sì, puoi vivere senza costruttori.

Certo, potresti 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 la fonte di un problema quando quel codice della piastra di caldaia non viene utilizzato in modo coerente in tutta l'applicazione.

Ma no, non è 'necessario' rigorosamente i propri costruttori. Naturalmente, non è nemmeno strettamente necessario "classi" e oggetti.

Ora, se il tuo obiettivo è quello di utilizzare una sorta di modello di fabbrica per la creazione di oggetti, che non si escludono a vicenda nell'uso dei costruttori durante l'inizializzazione dei tuoi oggetti.


Assolutamente. Si può anche vivere senza classi e oggetti, per cominciare.
JensG,

5
Tutti salutano il potente assemblatore!
Assapora Ždralo il

9

Il vantaggio di usare i costruttori è che rendono più semplice assicurarsi che non si abbia mai un oggetto non valido.

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

Ma quando un nuovo oggetto viene creato in uno stato non valido e è necessario chiamare alcuni setter per metterlo in uno stato valido in cui può essere utilizzato, si rischia che un consumatore della classe si dimentichi di chiamare questi setter o li chiama in modo errato e si finisce con un oggetto non valido.

Una soluzione alternativa potrebbe essere quella di creare oggetti solo attraverso un metodo factory che controlla la validità di ogni oggetto creato prima di restituirlo al chiamante.


3

$ obj = CLASS :: getInstance (). Quindi iniettiamo dipendenze usando setter ed eseguiamo l'inizializzazione iniziale usando $ obj-> initInstance ();

Penso che tu lo stia rendendo più difficile di quanto debba essere. Possiamo iniettare dipendenze bene attraverso il costruttore - e se ne hai molte, usa solo una struttura simile a un dizionario in modo da poter specificare quali vuoi 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 membri privati, se necessario.

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


2

In realtà stavo pensando a cose simili.

La domanda che ho posto è stata "Quale costruttore fa ed è possibile farlo diversamente?" Ho raggiunto queste conclusioni:

  • Assicura l'inizializzazione di alcune proprietà. Accettandoli come parametri e impostandoli. Ma questo potrebbe essere facilmente applicato dal compilatore. Annotando semplicemente i campi o le proprietà come "obbligatori" il compilatore verificherà 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.

  • Assicura che le proprietà siano valide. Ciò potrebbe essere facilmente raggiunto dalla condizione di asserzione. Ancora una volta dovresti semplicemente annotare le proprietà con le condizioni corrette. Alcune lingue lo fanno già.

  • Qualche logica di costruzione più complessa. I modelli moderni non raccomandano di farlo nel 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 alcuni grandi cambiamenti nella progettazione del linguaggio.

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


2

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

Nei linguaggi moderni (parlerò qui di C # in cui programmo) puoi limitare parti di codice che possono essere eseguite solo in un costruttore. Grazie al quale puoi evitare errori goffi. Una di queste cose è il modificatore di sola lettura :

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;
    }
}

Come raccomandato da Joachim Sauer in precedenza invece di usare Factoryschemi di design leggi Dependency Injection. Consiglierei di leggere Dependency Injection in .NET di Mark Seemann .


1

Creare un'istanza di un oggetto con un tipo in base ai requisiti è 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 credo che questo approccio crei incoerenze nel tuo codice, renda complessi i test * e "il futuro diventa 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 si sta creando un test nidificato di grandi dimensioni.


1

Per bilanciare alcune delle altre risposte, sostenendo:

Un costruttore ti dà la possibilità di impostare tutte le variabili membro dell'oggetto su uno stato valido ... non avrai mai un oggetto non valido, che ti salverà da molti bug.

e

Con un costruttore, un oggetto possiede la propria logica di costruzione in modo da garantire che non vi siano istanze non valide di tale classe.

Tali dichiarazioni implicano talvolta l'assunto che:

Se una classe ha un costruttore che, al momento della sua uscita, ha messo l'oggetto in uno stato valido e nessuno dei metodi della classe muta tale stato per renderlo non valido, è impossibile che un codice esterno alla classe rilevi un oggetto di quella classe in uno stato non valido.

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


0

Questo è in qualche modo aneddotico, ma di solito riservo costruttori per lo stato essenziale affinché l'oggetto sia completo e utilizzabile. Escludendo l'iniezione di setter, una volta avviato il costruttore, il mio oggetto dovrebbe essere in grado di eseguire le attività richieste.

Tutto ciò che può essere rinviato, lascio fuori dal costruttore (preparazione dei valori di output, ecc.). Con questo approccio, mi sento di non usare costruttori per altro che l'iniezione di dipendenza ha senso.

Ciò ha anche l'ulteriore vantaggio di cablare il processo di progettazione mentale per non fare nulla in anticipo. Non inizializzerai o eseguirai logiche che potrebbero non essere mai utilizzate perché nella migliore delle ipotesi tutto ciò che stai facendo è una configurazione di base per il lavoro da svolgere.

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.