Ha senso usare i costruttori e le interfacce fluide con gli inizializzatori di oggetti?


10

In Java e C #, è possibile creare un oggetto con proprietà che possono essere impostate all'inizializzazione definendo un costruttore con parametri, definendo ciascuna proprietà dopo aver costruito l'oggetto o utilizzando il modello di interfaccia builder / fluid. Tuttavia, C # 3 ha introdotto gli inizializzatori di oggetti e collezioni, il che significava che il modello del costruttore era in gran parte inutile. In un linguaggio senza inizializzatori, si potrebbe implementare un builder e usarlo in questo modo:

Vehicle v = new Vehicle.Builder()
                    .manufacturer("Toyota")
                    .model("Camry")
                    .year(1997)
                    .colour(CarColours.Red)
                    .addSpecialFeature(new Feature.CDPlayer())
                    .addSpecialFeature(new Feature.SeatWarmer(4))
                    .build();

Al contrario, in C # si potrebbe scrivere:

var vehicle = new Vehicle {
                Manufacturer = "Toyota",
                Model = "Camry",
                Year = 1997,
                Colour = CarColours.Red,
                SpecialFeatures = new List<SpecialFeature> {
                    new Feature.CDPlayer(),
                    new Feature.SeatWarmer { Seats = 4 }
                }
              }

... eliminando la necessità di un costruttore come mostrato nell'esempio precedente.

Sulla base di questi esempi, i costruttori sono ancora utili in C # o sono stati completamente sostituiti dagli inizializzatori?


are builders usefulperché continuo a vedere questo tipo di domande? Un Builder è un modello di progettazione - certo, può essere esposto come una caratteristica del linguaggio ma alla fine è solo un modello di progettazione. Un modello di progettazione non può essere "sbagliato". Potrebbe soddisfare o meno il tuo caso d'uso, ma ciò non rende sbagliato l'intero schema, ma solo la sua applicazione. Ricorda che alla fine della giornata i modelli di progettazione risolvono particolari problemi: se non li affronti, perché dovresti provare a risolverli?
VLAZ

@vlaz Non sto dicendo che i costruttori sono sbagliati - in effetti la mia domanda era se ci fossero casi d'uso per i costruttori, dato che gli inizializzatori sono un'implementazione del caso più comune in cui useresti i costruttori. Ovviamente, la gente ha risposto che un costruttore è ancora utile per impostare campi privati, che a sua volta ha risposto alla mia domanda.
svbnet,

3
@vlaz Per chiarire: sto dicendo che gli inizializzatori hanno in gran parte sostituito il modello "tradizionale" di interfaccia builder / fluid di scrivere una classe builder interna, quindi scrivere metodi di setter concatenati all'interno di quella classe che creano una nuova istanza di quella classe genitore. Sto Non dicendo che inizializzatori sono un sostituto per quella specifica builder modello; Sto dicendo che gli inizializzatori salvano gli sviluppatori che devono implementare quel modello di builder specifico, salvo i casi d'uso menzionati nelle risposte, che non rendono inutile il modello di builder.
svbnet,

5
@vlaz Non capisco la tua preoccupazione. "Stavi dicendo che uno schema di disegno non è utile" - no, Joe stava chiedendo se uno schema di disegno fosse utile. Come è una domanda sbagliata, sbagliata o sbagliata? Pensi che la risposta sia ovvia? Non penso che la risposta sia ovvia; questa mi sembra una buona domanda.
Tanner Swett,

2
@vlaz, i pattern singleton e service locator sono sbagliati. I modelli di progettazione Ergo possono essere sbagliati.
David Arno,

Risposte:


12

Come accennato da @ user248215, il vero problema è l'immutabilità. Il motivo per cui useresti un builder in C # sarebbe quello di mantenere l'esplicitazione di un inizializzatore senza dover esporre proprietà impostabili. Non è una questione di incapsulamento, motivo per cui ho scritto la mia risposta. L'incapsulamento è abbastanza ortogonale in quanto invocare un setter non implica ciò che il setter effettivamente fa o ti lega alla sua implementazione.

La prossima versione di C #, 8.0, introdurrà probabilmente una withparola chiave che consentirà di inizializzare gli oggetti immutabili in modo chiaro e conciso senza la necessità di scrivere costruttori.

Un'altra cosa interessante che puoi fare con i costruttori, al contrario degli inizializzatori, è che possono risultare in diversi tipi di oggetti a seconda della sequenza dei metodi chiamati.

Per esempio

value.Match()
    .Case((DateTime d) => Console.WriteLine($"{d: yyyy-mm-dd}"))
    .Case((double d) => Console.WriteLine(Math.Round(d, 4));
    // void

var str = value.Match()
    .Case((DateTime d) => $"{d: yyyy-mm-dd}")
    .Case((double d) => Math.Round(d, 4).ToString())
    .ResultOrDefault(string.Empty);
    // string

Solo per chiarire l'esempio sopra, si tratta di una libreria di corrispondenza dei modelli che utilizza il modello del builder per creare una "corrispondenza" specificando i casi. I casi vengono aggiunti chiamando il Casemetodo passandogli una funzione. Se valueè assegnabile al tipo di parametro della funzione, viene richiamato. Puoi trovare il codice sorgente completo su GitHub e, poiché i commenti XML sono difficili da leggere in testo semplice, ecco un link alla documentazione costruita da SandCastle (vedi la sezione Note )


Non vedo come questo non possa essere fatto un inizializzatore di oggetti, usando un IEnumerable<Func<T, TResult>>membro.
Caleth,

@Caleth In realtà è stato qualcosa con cui ho sperimentato ma ci sono alcuni problemi con questo approccio. Non consente clausole condizionali (non mostrate ma utilizzate e dimostrate nella documentazione collegata) e non consente l'inferenza di tipo di TResult. In definitiva, l'inferenza di tipo aveva molto a che fare con questo. Volevo anche che "assomigliasse" a una struttura di controllo. Inoltre non volevo usare un inizializzatore poiché ciò avrebbe permesso la mutazione.
Aluan Haddad,

12

Gli inizializzatori di oggetti richiedono che le proprietà debbano essere accessibili dal codice chiamante. I costruttori nidificati possono accedere ai membri privati ​​della classe.

Se si desidera rendere Vehicleimmutabile (rendendo privati ​​tutti i setter), è possibile utilizzare il builder nidificato per impostare le variabili private.


0

Tutti hanno scopi diversi !!!

I costruttori possono inizializzare i campi contrassegnati readonlynonché i membri privati ​​e protetti. Tuttavia, sei piuttosto limitato in ciò che puoi fare all'interno di un costruttore; dovresti evitare di passare thisa metodi esterni, ad esempio, e dovresti evitare di chiamare membri virtuali, poiché potrebbero essere eseguiti nel contesto di una classe derivata che non si è ancora costruita. Inoltre, i costruttori sono garantiti per l'esecuzione (a meno che il chiamante non stia facendo qualcosa di molto insolito), quindi se si impostano i campi nel costruttore, il codice nel resto della classe può presumere che tali campi non saranno nulli.

Gli inizializzatori vengono eseguiti dopo la costruzione. Ti permettono solo di chiamare proprietà pubbliche; non è possibile impostare campi privati ​​e / o di sola lettura. Per convenzione, l'atto di impostare una proprietà dovrebbe essere piuttosto limitato, ad esempio dovrebbe essere idempotente e avere effetti collaterali limitati.

Costruttore metodi del sono metodi effettivi e pertanto consentono più di un argomento e, per convenzione, possono avere effetti collaterali tra cui la creazione di oggetti. Sebbene non possano impostare campi di sola lettura, possono praticamente fare qualsiasi altra cosa. Inoltre, i metodi possono essere implementati come metodi di estensione (come praticamente tutte le funzionalità LINQ).

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.