彼は同時に正しいことも間違っていることもあります。
のようなものBinaryFormatter
では、これは問題ではありません。シリアル化されたストリームにはフルタイプのメタデータが含まれているため、次の場合は次のようになります。
[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
シリアル化すると、ストリームに「obj
I'ma」が含まれます。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
カスタムctorルートを使用する場合は、XmlSerializer
インスタンスをキャッシュして再利用することが重要です。それ以外の場合は、使用ごとに新しい動的アセンブリがロードされます-非常に高価です(アンロードできません)。単純なコンストラクターを使用すると、モデルがキャッシュされて再利用されるため、単一のモデルのみが使用されます。
YAGNIは、最も単純なオプションを選択する必要があると指示しています。を使用[XmlInclude]
すると、複雑なコンストラクターが不要になり、シリアライザーのキャッシュについて心配する必要がなくなります。ただし、他のオプションがあり、完全にサポートされています。
フォローアップの質問を再確認してください。
「ファクトリパターン」とは、おそらくIoC / DIまたは同様のフレームワークが原因で、コードが認識しない 場合について話していることです。SomeConcrete
だからあなたは持っているかもしれません:
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>
これが必要な場合は、トランスポートに対応する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;}
}
これは、次のような他のすべてのニグルにも当てはまります。
- なぜパラメーターなしのコンストラクターが必要なのですか?
- コレクションプロパティにセッターが必要なのはなぜですか?
- 不変型を使用できないのはなぜですか?
- なぜ私のタイプはパブリックでなければならないのですか?
- 複雑なバージョン管理を処理するにはどうすればよいですか?
- さまざまなデータレイアウトを持つさまざまなクライアントを処理するにはどうすればよいですか?
- なぜインターフェースを使用できないのですか?
- などなど
これらの問題が常に発生するとは限りません。ただし、そうする場合は、DTO(またはいくつか)を導入すれば、問題は解決します。これをインターフェースに関する質問に戻します。DTOタイプはインターフェースベースではない場合がありますが、ランタイム/ビジネスタイプはインターフェースベースにすることができます。