2009-08-25 251 views
10

我想序列化,並且我正面臨着抽象類的問題。抽象類的序列化

我搜索了一個答案,我發現this blogitem。 我嘗試過那樣的工作。

好吧,非常好。但檢查出評論的項目:

這種方法似乎是躲在 真正的問題,這是一個不準確的 實現面向對象的設計 模式,即工廠模式。

必須將基類更改爲 參考任何新的工廠類是 自我挫敗。

隨着一點點後認爲,代碼 可以改變到任何派生 類型可以與 抽象類(通過的 接口奇蹟)相關聯,並且沒有XmlInclude將是需要 。

我建議進一步研究 工廠模式,這似乎是 你在這裏試圖實現什麼。

評論者在說什麼?他有點模糊。有人能更詳細地解釋它嗎(舉一個例子)?或者他只是在胡說八道?

更新(閱讀後第一個答案)

爲什麼在評註談論

工廠模式

的代碼是可以改變的到任何地方 派生類型可以與 抽象類(通過 奇蹟的接口)

關聯?

他想製作一個界面嗎?

public interface IWorkaround 
{ 
    void Method(); 
} 

public class SomeBase : IWorkaround 
{ 
    public void Method() 
    { 
     // some logic here 
    } 
} 

public class SomeConcrete : SomeBase, IWorkaround 
{ 
    public new void Method() 
    { 
     base.Method(); 
    } 
} 

回答

37

他既是對與否的同時。

隨着諸如BinaryFormatter這樣的事情,這是一個非問題;序列化流包含了完整的元數據類型,所以如果你有:

[Serializable] abstract class SomeBase {} 
[Serializable] class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 

和序列obj,那麼它包含「我是SomeConcrete」的流。這使得生活變得簡單,但冗長,特別是在重複時。它也很脆弱,因爲它在反序列化時需要相同的實現;對於不同的客戶端/服務器實現或長期存儲都不利。

XmlSerializer(我猜這個博客正在討論),沒有元數據 - 但元素名稱(或xsi:type屬性)用於幫助識別使用哪個元數據。爲了這個工作,序列化程序需要事先知道什麼名字映射到哪些類型。

最簡單的方法是用我們知道的子類修飾基類。然後序列化程序可以檢查其中的每一個(以及任何其他特定於xml的屬性),以便發現當它看到<someConcreteType>元素時,該元素映射到SomeConcrete實例(請注意,名稱不需要匹配,因此它可以'只需按名稱查找它)。

[XmlInclude(typeof(SomeConcrete))] 
public abstract class SomeBase {} 
public class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase)); 
ser.Serialize(Console.Out, obj); 

然而,如果他是一個純粹的(或數據不可用),則存在另一種;您可以通過重載構造函數將所有這些數據分別指定爲XmlSerializer。例如,您可以從配置(或者可能是一個IoC容器)中查找一組已知的子類型,並手動設置構造函數。這不是非常棘手,但它是非常棘手,它是不值得的,除非你實際上需要它

public abstract class SomeBase { } // no [XmlInclude] 
public class SomeConcrete : SomeBase { } 
... 
SomeBase obj = new SomeConcrete(); 
Type[] extras = {typeof(SomeConcrete)}; // from config 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras); 
ser.Serialize(Console.Out, obj); 

此外,與XmlSerializer如果你去自定義構造函數的路線,其緩存和重新使用XmlSerializer實例是很重要的;否則每次使用都會加載一個新的動態程序集 - 非常昂貴(它們不能被卸載)。如果使用簡單的構造函數,它會緩存並重新使用該模型,因此只使用一個模型。

YAGNI指示我們應該選擇最簡單的選項;使用[XmlInclude]可以消除對複雜構造函數的需求,並且無需擔心緩存序列化程序。另一種選擇是在那裏,並且完全支持。


重新您的後續問題:

通過「工廠模式」,他在談論您的代碼不知道SomeConcrete的情況,可能是由於的IoC/DI或類似的框架;所以你可能有:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe); 

哪個計算出相應的SomeBase具體實施,其實例化並把它回來。顯然,如果我們的代碼不知道具體類型(因爲它們只在配置文件中指定),那麼我們不能使用XmlInclude;但我們可以解析配置數據並使用ctor方法(如上所述)。實際上,大多數時候XmlSerializer與POCO/DTO實體一起使用,所以這是人爲的關注。

和重新接口;同樣的事情,但更靈活(一個接口不需要類型層次結構)。但XmlSerializer不支持此模式。坦率地說,艱難;這不是它的工作。它的工作是讓你存儲和傳輸數據。未執行。任何xml架構生成的實體都不會方法。數據是具體的,而不是抽象的。只要你認爲「DTO」,界面辯論就不是問題。因無法在其邊界上使用界面而煩惱的人不會分心,即他們正在嘗試做的:

Client runtime entities <---transport---> Server runtime entities 

,而不是限制較少

Client runtime entities <---> Client DTO <--- transport---> 
      Server DTO <---> Server runtime entities 

現在,在許多(大多數?)情況下,DTO和實體可以是相同的;但是如果你想要做一些交通工具不喜歡的事情,那就引入一個DTO;不要與串行器對抗。同樣的道理,當人們都在努力寫自己的對象適用:

class Person { 
    public string AddressLine1 {get;set;} 
    public string AddressLine2 {get;set;} 
} 

爲形式的XML:

<person> 
    <address line1="..." line2="..."/> 
</person> 

如果你想要這個,intoduce對應於運輸DTO,和之間進行映射你的實體和DTO:

// (in a different namespace for the DTO stuff) 
[XmlType("person"), XmlRoot("person")] 
public class Person { 
    [XmlElement("address")] 
    public Address Address {get;set;} 
} 
public class Address { 
    [XmlAttribute("line1")] public string Line1 {get;set;} 
    [XmlAttribute("line2")] public string Line2 {get;set;} 
} 

這也適用於所有其他niggles,如:

  • 爲什麼我需要無參數構造函數?
  • 爲什麼我需要一個setter用於我的收藏屬性?
  • 爲什麼我不能使用不可變類型?
  • 爲什麼我的類型必須公開?
  • 我該如何處理複雜的版本控制?
  • 我該如何處理具有不同數據佈局的不同客戶端?
  • 爲什麼我不能使用接口?
  • 等,等

你並不總是有這些問題;但如果你這樣做 - 引入一個DTO(或幾個),你的問題就會消失。回顧關於接口的問題; DTO類型可能不是基於接口的,但是您的運行時/業務類型可以。

+0

非常感謝你的信息。 – Natrium 2009-08-26 13:14:33