web-dev-qa-db-ja.com

インターフェイスとDTOおよび応答クラスの具象型

外部からの入力が必要だと感じたディスカッションがありました。通常は、具体的なタイプ(リストや注入可能なサービスなど)ではなく、常にインターフェースを使用します。たとえば、リストでは、代わりにIEnumerableをリストで渡すだけです。カスタムタイプ(CustomClassではなくICustomClass)についても同様です。これは、webapi、serilogなどのロギングフレームワークなどでは正常に機能します。

しかし、実装の詳細により、シリアル化インターフェイスを処理できないカスタムコードに遭遇しました。インターフェースの代わりに(そしてシリアライゼーションを調整する代わりに)具体的な型を維持するための議論は this post に要約されました。そして、XML文書で作業する場合、シリアル化/逆シリアル化が必要な場合(?).

編集:私の説明が不明瞭/曖昧だった可能性があることに気づきました。私は個人的には、DTO自体(IDTOなど)のインターフェースを作成することはありません。私はそれに何の意味もないので。私はおそらくそれを交換するつもりはなく、私はそれを例えば注入しません。ただし、たとえば、DTOにIEnumerableプロパティを含めて、たとえばListやType []を優先させることもよくあります。常にシンプルに保つために、インターフェイスによって表されるカスタムクラスがあったことを思い出せません。しかし、おそらく同じ質問が続くかもしれない、これに関して私が提起できるシナリオがあるでしょう。

私が喜んでいくつかの入力を得るものは何ですか:

通常のREST jsonを出力するAPI)でDTOおよび応答(APIやログダンプを通じて返されるときにシリアル化される可能性がある)のインターフェイスを回避するための適切な具体的な技術的引数はありますか?

3
Base

一般に、できるだけ抽象的な型を受け入れて、できるだけ具体的な型を返したいのですが、遭遇した状況は、抽象型と具象型の問題ではありません。引数がシリアライズ可能であることを確認する責任を呼び出し元に持たせたい場合は、それをインターフェースで指定する必要があります。つまり、IListISerializableのようなものの両方が必要です。

または、IListインターフェースだけを使用して、シリアル化を自分で実装することもできます。ただし、すべてのIListsがシリアル化可能であるとは限りません。たとえば、無限リストをどのようにシリアル化しますか?

そうは言っても、複数のインターフェイスを必要とするパラメーターのC#構文は狂っています。あなたが行うYAGNI引数があります本当にIListを実装するすべてをサポートする必要がありますか?読みやすさのトレードオフの価値はありますか?

設計原則はバランスをとる必要があるため、明確にするために、少し抽象化をあきらめることも問題ありません。

3
Karl Bielefeldt

避けるべきいくつかの理由interfaces


適切なツールを使用

interfaceはツールであり、正しい方法で使用すると、簡素化され、より大規模な開発が可能になります。

不十分に使用すると、複雑さが増し、使用が妨げられます。

[〜#〜] dto [〜#〜]

データ型オブジェクトはクラスのインスタンスのようなオブジェクトではありません-ソフトウェアエンジニアは意味をオーバーロードするのが好きだからです...

  1. オブジェクトa.k.a.オブジェクト指向プログラミングのオブジェクトインスタンスは、モノの状態をキャプチャするためのモデリング手法であり、モノがその状態(通信プロトコル)でメッセージにどのように応答するかを示します。
  2. オブジェクトa.k.a.メモリオブジェクトは、特定のレイアウトで編成されたゼロとワンの領域です。

最初のケースでは、interfaceを使用して、同じ通信プロトコルをまだサポートしているさまざまなオブジェクトインスタンスを置き換えることができます。典型的な例はShapeインターフェースであり、Shapeとやり取りしたい関数がSquareと同様にCircleと同等に通信できるようにします。またはRhombus

2番目のケースは、特定の場所のビットをどのように認識するかについての説明です。これには、これらのビットがどの状態にあるべきか、またはどのように変更する必要があるかについての期待が含まれます。例:Whatever the field x is byte 6 to 6 + valueof(byte 5). Field y at byte 16 can be seen as a signed two's-complement little-endian 64bit integer。ただし、これらの期待を真実にするための実装はありません。実装がなければ、意味は文字通り見る人の目にあります。したがって、ここでは通信が行われていないため、interfaceが存在しない可能性があります。

注:特に下に近づくと、技術的にはメモリオブジェクトの周囲にinterfaceがあります。それは何でも行くほど寛容なので、この抽象化レベルでは通信プロトコルとして実際には何も実行せず、オブジェクト指向オブジェクトのように見せること以外に意味はありません。

なぜ混乱するのですか?

ただ明確にすると、これがObjectという用語が非常に簡単に混同される理由です-classinterfacedataimplementation

  • interfaceは、アクセス可能な関数とプロパティの名前とシグネチャ、およびそれらが対話を通じてどのように変化するかであり、オブジェクト指向を提供します。
  • dataは、クラス内に保持されるプライベート変数であり、関数が操作する予測可能なメモリオブジェクトを形成します。
  • implementationは、内部のメモリオブジェクトと他の通信の状態に基づいてオブジェクト指向の動作を提供する関数本体とプロパティアクセサーです。

セキュリティ

常に良い反論

現在、オブジェクトインスタンスは通常ネットワーク経由で送信されていません。

ネットワーク経由で送信するには、独自のインターフェースと実装も必要です。 JavaScriptはここでの象徴的な例であり、eval(my_received_js_object)を使用してはならない理由をオンラインで説明する記事が多すぎて、オブジェクトを送信するのは悪い考えだと感じられます...

しかし、これは取るに足らないことです。オブジェクトインスタンスのシリアル化には、はるかに重要な問題があります。

オブジェクトインターフェイスのインスタンスを受け取る方法

ここにposerがあります:

送信されたメッセージが表すオブジェクトインスタンス(または少なくとも合理的な類似物)を受信者が正確に再作成できるように、時間/空間(ハードドライブまたはネットワーク)でメッセージをどのように送信しますか?

ここでの問題は、interfaceが数百の異なる実装を持つ可能性があることです。それでは、どの実装を意味しますか?

シリアル化フレームワークがそのようなメッセージを受け取っている場合は、次の方法でこの問題を解決できます。

  • そのインターフェースの現在利用可能な実装について反映し、正しいものを推測します。
  • 適切な実装を示すために、フレームワークを使用しているプログラムに尋ねます。
  • 知っているすべての実装を提供するinterface...
  • 空中に手を上げて、あきらめてください。

オブジェクトインターフェイスのインスタンスを送信する方法

一方、誰かがメッセージを作らなければなりませんでした。彼らはそれを送信するためにどのようにインターフェースに問い合わせたでしょうか? ホラーストーリーとして、interface IDispose { void dispose(); };をシリアル化することを検討してください

  • オブジェクトのタイプ情報を反映し、すべてのプロパティにアクセスするだけでオブジェクトを保存できることを期待します。
  • データを取得するための正しいプロパティ/メソッドにラベルを付けるようにプログラムに依頼します。
  • プログラムに代わって送信するメッセージを手作りするよう依頼します。
  • インターフェースの背後にある実装に侵入するメカニズムがあります(カプセル化を完全にブレーキング)
  • 実装と状態を含むオブジェクト全体を送信し、待機...それは安全ではありません。
  • あきらめて、代わりにc ++テンプレートメタプログラムを書いてください...

代替案?

シリアル化フレームワークが送受信する正確なメモリオブジェクトを指示します。次に、送信者/受信者に、それらのメモリオブジェクトとの間のドメインのマッピングを処理するためのサポートを提供します。

実際に使用するシリアル化フレームワークは重要ではありません。これらはすべて基本的にこの方法で機能します。それらには、簡単にシリアライズ/デシリアライズできるいくつかのコアデータオブジェクトがあります。

これらのコア表現は、何らかの形のマッピングを通じてオブジェクトインスタンスから取得されます。マッピングは簡単に自動化される場合があります(すべてのプロパティを取得するだけです)が、実装の動作がオブジェクト指向であるほど、次のような特別なディレクティブを提供する必要があります。

  • このプロパティには触れないでください。
  • この関数を呼び出して、シリアル化を要求します。
  • このコンストラクタを使用
  • デフォルトのコンストラクタを使用して、このメソッドを呼び出します。

受信者では、これらのコアモデルは、関数呼び出しまたは何らかの形のDOM(ドキュメントオブジェクトモデル)の観点から生成されます。次に、自動的に、または特別なディレクティブ/ハンドラーを介して、受信者が操作できるObjectインスタンスにマッピングされます。

アーキテクチャ的に

Objectインスタンスは、自己完結型の状態単位であり、そのオブジェクトだけが認識し、そのオブジェクトによって完全に管理されます。つまり、シリアル化/非シリアル化もそのオブジェクトの実装によって処理されます-例外はありません。

もちろんオブジェクトの実装はシリアライゼーションフレームワークを活用できますが、それはおそらくドメインエンティティとフレームワークの間に結合を課します。それは決して良い兆候ではありません。

コードを健全かつクリーンに保つということは、オブジェクトインスタンスがメモリオブジェクトを返し、それがフレームワークによって簡単にシリアル化されることを意味します。これはカップリングが低く、フレームワークを簡単に変更できることを保証します。

2
Kain0_0

通常、具象型の代わりに常にインターフェースを使用します。

インターフェースを追加するたびに、別のタイプを追加し、抽象化レベルを作成するため、コードが複雑になります。インターフェースにはコストがかかるため、それらを使用する十分な理由も必要です。したがって、「通常は常に具象型の代わりにインターフェースを使用する」は良いアプローチではありません。それらが利点を加えるときインターフェイスを使用しなさい。

たとえば、DTOがあるとします。

public class DTO
{
    public string Name;
    public string Address;
}

パブリックに変更可能であり、これで問題ないかもしれません。それは簡単なので、それがうまくいけば、すべてが順調です。しかし、それを不変にしたい場合があるので、次のようにします。

public class DTO
{
    public DTO(string name, string address) => (Name, Address) = (name, address);

    public string Name { get; }
    public string Address { get; }
}

しかし、今は問題があります。多くのシリアル化ツールは、コンストラクタと読み取り専用プロパティのこのような混合を処理できません。したがって、型を読み取り専用として公開するインターフェイスでこれを処理できます。

public interface IDTO
{
    string Name { get; }
    string Address { get; }
}

internal class DTO : IDTO
{
    public string Name { get; internal set; }
    public string Address { get; internal set; }
}

これで、一般的にIDTOを介してdtoを公開し(その後、パブリックに読み取り専用になります)、シリアル化コードにDTOへの直接アクセスを提供して、これらのプロパティに書き込むことができるようになります。

しかし、多くの複雑さを追加しました。あなたはそれが書き込めないことの利点を公に、その余分な複雑さと比較検討する必要があります。メリットが十分に価値がある場合にのみ、その複雑さを招き、したがってインターフェースのみを使用します。

1
David Arno