2012-09-06 11 views
17

Per vari motivi mi piacerebbe iniziare a usare tipi più immutabili nei disegni. Al momento, sto lavorando con un progetto che ha una classe esistente in questo modo:Disegno immutabile: trattare con il Costruttore Follia

public class IssueRecord 
{ 
    // The real class has more readable names :) 
    public string Foo { get; set; } 
    public string Bar { get; set; } 
    public int Baz { get; set; } 
    public string Prop { get; set; } 
    public string Prop2 { get; set; } 
    public string Prop3 { get; set; } 
    public string Prop4 { get; set; } 
    public string Prop5 { get; set; } 
    public string Prop6 { get; set; } 
    public string Prop7 { get; set; } 
    public string Prop8 { get; set; } 
    public string Prop9 { get; set; } 
    public string PropA { get; set; } 
} 

Questa classe rappresenta un certo formato su disco che ha davvero questo molte proprietà, quindi il refactoring in pezzi più piccoli è abbastanza molto fuori questione a questo punto.

Ciò significa che il costruttore di questa classe ha davvero bisogno di avere 13 parametri in un progetto immutabile? In caso contrario, quali passi potrei fare per ridurre il numero di parametri accettati nel costruttore se dovessi rendere questo progetto immutabile?

+0

Qualcosa di esterno alla classe ha bisogno di impostare i valori o può la classe stessa impostare i valori? Se la classe può farlo, allora puoi avere un costruttore senza parametri. –

+1

Vedere: http://stackoverflow.com/questions/355172/how-to-design-an-immutable-object-with-complex-initialization?rq=1 – aquinas

+0

Come si stanno inizializzando queste proprietà ora? –

risposta

13

Ciò significa che il costruttore di questa classe ha davvero bisogno di avere 13 parametri in un progetto immutabile?

In generale, sì. Un tipo immutabile con 13 proprietà richiederà alcuni metodi per inizializzare tutti questi valori.

Se non sono tutti utilizzati o se determinate proprietà possono essere determinate in base alle altre proprietà, è possibile che uno o più costruttori con overload abbiano meno parametri. Tuttavia, un costruttore (indipendentemente dal fatto che il tipo sia immutabile) in realtà dovrebbe inizializzare completamente i dati per il tipo in modo che il tipo sia logicamente "corretto" e "completo".

Questa classe rappresenta un formato su disco che ha davvero molte proprietà, quindi refactoring in bit più piccoli è praticamente fuori questione a questo punto.

Se il "formato su disco" è qualcosa che viene determinato in fase di esecuzione, si potrebbe avere un metodo factory o costruttore che prende i dati di inizializzazione (vale a dire:? Il nome del file, ecc) e costruisce il pieno-inizializzato scrivi per te

2

È possibile creare una struttura, ma in tal caso si dovrà comunque dichiarare la struttura. Ma ci sono sempre array e così via. Se sono tutti dello stesso tipo di dati, puoi raggrupparli in diversi modi, come un array, un elenco o una stringa. Sembra che tu abbia ragione, tutti i tuoi tipi immutabili devono passare attraverso il costruttore in qualche modo, tempo attraverso 13 parametri, o attraverso una struct, array, lista, ecc ...

+0

Oh, ho appena realizzato che molti di loro sono archi, quindi era giusto dire di usare un array. –

+2

Un array in realtà non funziona perché distrugge il significato semantico dei parametri nella struttura. Qualcuno che usa la classe dovrebbe ricordare che il quinto elemento dell'array significava "Dettagli" o qualcosa del genere che è peggio di lasciare la classe mutabile. –

+0

Giusto, quindi una struttura è l'unica altra opzione reale. Inoltre, se sono corretto, in C#, una struttura può utilizzare i valori predefiniti, che potrebbero essere utili nella tua situazione. –

3

Forse mantieni la tua classe corrente così com'è, fornendo se possibile impostazioni predefinite e rinominando IssueRecordOptions. Usa questo come parametro di inizializzazione singolo per il tuo IssueRecord immutabile.

+0

Questa è una buona opzione se il requisito di immutabilità è necessario solo per una parte del sistema. Hai ancora a che fare con tipi mutabili, tuttavia, poiché devi modificare il tipo "Opzioni" originale. –

3

È possibile utilizzare una combinazione di argomenti nominati e opzionali nel costruttore. Se i valori sono sempre diversi, allora sì, sei bloccato con un costruttore folle.

14

Per ridurre il numero di argomenti è possibile raggrupparli in set sensibili, ma per ottenere oggetti veramente immutabili è necessario inizializzarli nel metodo costruttore/fabbrica.

Alcune varianti sono la creazione di una classe "builder" che è possibile configurare con un'interfaccia fluente e che richiedere l'oggetto finale. Questo avrebbe senso se in realtà si prevede di creare molti di questi oggetti in diverse parti del codice, altrimenti molti argomenti in un singolo punto potrebbero essere accettabili.

var immutable = new MyImmutableObjectBuilder() 
    .SetProp1(1) 
    .SetProp2(2) 
    .Build(); 
0

Se l'intento è vietare le assegnazioni durante il tempo di compilazione, è necessario attenersi alle assegnazioni del costruttore e ai setter privati. Tuttavia ha numerosi svantaggi - non sono in grado di utilizzare la nuova inizializzazione di membri, né xml deseralization ed ecc

Vorrei suggerire qualcosa di simile:

public class IssuerRecord 
    { 
     public string PropA { get; set; } 
     public IList<IssuerRecord> Subrecords { get; set; } 
    } 

    public class ImmutableIssuerRecord 
    { 
     public ImmutableIssuerRecord(IssuerRecord record) 
     { 
      PropA = record.PropA; 
      Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r)); 
     } 

     public string PropA { get; private set; } 
     // lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5. 
     public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; } 

     // you may want to get a mutable copy again at some point. 
     public IssuerRecord GetMutableCopy() 
     { 
      var copy = new IssuerRecord 
          { 
           PropA = PropA, 
           Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy())) 
          }; 
      return copy; 
     } 
    } 

IssuerRecord qui è molto più descrittivo e utile. Quando lo passi da qualche altra parte, puoi facilmente creare una versione immutabile. Il codice che funziona su immutabile dovrebbe avere una logica di sola lettura, quindi non dovrebbe importare se è lo stesso tipo di IssuerRecord. Creo una copia di ogni campo invece di semplicemente avvolgere l'oggetto perché potrebbe ancora essere cambiato da qualche altra parte, ma potrebbe non essere necessario in particolare per le chiamate di sincronizzazione sequenziali. Tuttavia è più sicuro conservare la copia completa e immutabile in alcune raccolte "per dopo". Può essere un wrapper per le applicazioni quando si desidera che alcuni codici proibiscano le modifiche ma hanno ancora la possibilità di ricevere aggiornamenti allo stato dell'oggetto.

var record = new IssuerRecord { PropA = "aa" }; 
if(!Verify(new ImmutableIssuerRecord(record))) return false; 

se si pensa in termini C++, si può vedere come ImmutableIssuerRecords "IssuerRecord const". Devi prendere extracare anche se per proteggere gli oggetti che sono di proprietà del tuo oggetto immutabile è per questo che suggerisco di creare una copia per tutti i bambini (esempio Subrecords).

ImmutableIssuerRecord.Subrecors è IEnumerable qui e manca il conte e questo [], ma IReadOnlyList è venuta a 4.5 ed è possibile copiarlo da docs se voleva (e rendere più facile la migrazione in seguito).

ci sono altri approcci come pure, come ad esempio Freezable:

public class IssuerRecord 
{ 
    private bool isFrozen = false; 

    private string propA; 
    public string PropA 
    { 
     get { return propA; } 
     set 
     { 
      if(isFrozen) throw new NotSupportedOperationException(); 
      propA = value; 
     } 
    } 

    public void Freeze() { isFrozen = true; } 
} 

che rende il codice meno leggibile di nuovo, e non fornisce una protezione in fase di compilazione. ma puoi creare oggetti come normali e poi congelarli dopo che sono pronti.

Il modello di generatore è anche qualcosa da considerare, ma aggiunge troppo codice di "servizio" dal mio punto di vista.

+0

È possibile utilizzare la deserializzazione XML bene. Hai solo bisogno di usare DataContractSerializer invece di XmlSerializer. Per me va bene. –

+0

Sì, questo era il mio secondo suggerimento, è solo che questo rende l'applicazione simile a una macchina Rube Goldberg. –