2012-09-06 31 views
17

由於各種原因,我想在設計中使用更多不可變類型。目前,我與它有一個現有類這樣一個項目工作:不可變設計:處理構造函數錯誤

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

此類表示一些磁盤上的格式,它確實有這麼多的特性,所以它重構爲更小的位是相當在這一點上很多問題都不存在。

這是否意味着,在這個類的構造函數確實需要有在不變的設計13個參數?如果不是,如果我要使這個設計不可變,我可以採取哪些步驟來減少構造函數中接受的參數數量?

+0

在類的外部需要設置值,還是類本身可以設置值?如果類可以做到這一點,那麼你可以有一個無參數的構造函數。 –

+1

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

+0

你現在如何初始化這些屬性? –

回答

13

這是否意味着這個類的構造函數真的需要在一個不可變的設計中有13個參數?

一般情況下,是的。具有13個屬性的不可變類型將需要一些初始化所有這些值的方法。

如果不使用的話,或者如果某些屬性可以根據其他屬性來決定,那麼你可以或許有更少的一個或多個參數的重載構造。然而,構造函數(不論類型是否不可變)實際上應該是以類型邏輯「正確」和「完整」的方式完全初始化類型的數據。

這個類代表了一些磁盤上的格式,它確實有這麼多的屬性,所以現在將它重構爲較小的位數幾乎是不可能的。

如果「磁盤格式」是,就是BEING在運行時確定的東西,你可能有一個工廠方法或構造函數需要初始化數據(即:文件名等),並建立全初始化爲你輸入。

2

你可以做一個結構,但你仍然要申報結構。但總是有數組等。如果它們都是相同的數據類型,則可以通過多種方式對它們進行分組,例如數組,列表或字符串。它確實顯示你是對的,但你的所有不可變類型必須以某種方式穿過構造函數,通過13個參數,或通過結構,數組,列表等等...

+0

哦,我只是意識到,他們中的大多數是字符串,所以我說是使用數組是正確的。 –

+2

一個數組真的不起作用,因爲它破壞了結構中參數的語義含義。有人使用這個類將不得不記住數組中的第5個元素意味着「Details」或類似那種比離開類可變更糟糕的東西。 –

+0

對,那麼一個結構是你唯一的其他實際選項。另外,如果我是正確的,在C#中,結構可以使用默認值,這對您的情況可能很有用。 –

3

也許保留你當前的類如果可能,請儘可能提供合理的默認值,並重命名爲IssueRecordOptions。將此用作單個初始化參數給您的不可變IssueRecord。

+0

如果系統的一部分只需要不變性要求,這是一個不錯的選擇。但是,您仍然在處理可變類型,因爲您必須改變原始的「選項」類型。 –

3

您可以在構造函數中使用命名參數和可選參數的組合。如果這些值總是不同,那麼是的,你被一個瘋狂的構造函數卡住了。

14

要減少數量的參數,你可以將它們組合成明智集,但有你在構造函數/工廠方法來初始化它真正的不可變對象。

有些變化是創建可以用流利的接口配置,比要求的最終對象「建設者」類。如果你實際上計劃在代碼的不同位置創建許多這樣的對象,那麼這是有意義的,否則許多參數在一個地方可能是可以接受的折衷。

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

如果你的意圖是在編譯期間禁止賦值,那麼你必須堅持構造函數賦值和私有setter。但是它有許多缺點 - 你是不是能夠使用新的成員初始化,也不XML deseralization等

我建議是這樣的:

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得多的描述和有用的。當你將它傳遞給其他地方時,你可以輕鬆創建不可變的版本。對不可變的代碼應該只有只讀邏輯,所以它不應該在乎它是否與IssuerRecord類型相同。我創建了每個字段的副本,而不僅僅是包裝對象,因爲它可能仍然在其他地方被更改,但是對於連續同步調用而言可能不是必需的。然而,將完整的不可變副本存儲到某些集合中「以後」更安全。它可能是一個包裝,但應用程序當你想要一些代碼禁止修改,但仍然有能力接收對象狀態的更新。

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

,如果你認爲在C++方面,你可以看到ImmutableIssuerRecords爲 「IssuerRecord常量」。你必須採取額外的措施來保護你的不變對象所擁有的物體,這就是爲什麼我建議爲所有的孩子創建一個副本(Subrecords示例)。

ImmutableIssuerRecord.Subrecors在這裏是IEnumerable並且沒有Count和this [],但是IReadOnlyList在4.5版本中出現,如果需要的話可以從文檔複製它(並且稍後可以輕鬆遷移)。

有其他的方法,以及,如可凍結:

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

這使得代碼再次少可讀的,並且不提供編譯時保護。但您可以像平常一樣創建對象,然後在準備好後凍結它們。

構建模式也是需要考慮的事情,但是從我的角度來看它增加了太多的「服務」代碼。

+0

您可以使用XML反序列化。您只需使用DataContractSerializer而不是XmlSerializer。那個我能接受。 –

+0

是的,這是我的第二個建議,它只是讓您的應用程序看起來像Rube Goldberg Machine。 –