型マッピングと拡張メソッドに関するベストプラクティス


15

型のマッピングとC#での拡張メソッドの使用に関するベストプラクティスについて質問したいです。このトピックは過去数年にわたって何度も議論されてきましたが、私は多くの投稿を読みましたが、まだ疑問があります。

私が遭遇した問題は、私が所有するクラスを「変換」機能で拡張することでした。あるロジックで使用されるオブジェクトを表すクラス「Person」があるとしましょう。また、外部APIからの応答を表すクラス「Customer」もあります(実際には、複数のAPIがあるため、各APIの応答を共通のタイプ:Personにマッピングする必要があります)。両方のクラスのソースコードにアクセスでき、理論的には独自のメソッドを実装できます。データベースに保存できるように、CustomerをPersonに変換する必要があります。プロジェクトは自動マッパーを使用しません。

私は4つの可能な解決策を考えています:

  1. Consumerクラスの.ToPerson()メソッド。シンプルですが、私には単一責任パターンを壊しているように見えます。特に、Consumerクラスは他のクラス(別の外部APIに必要なもの)にもマップされるため、複数のマッピングメソッドを含める必要があります。

  2. Consumerを引数として取るPersonクラスのコンストラクターをマッピングします。また、簡単で、単一責任パターンを壊すようにも見えます。複数のマッピングコンストラクターが必要です(別のAPIからのクラスがあり、Consumerと同じデータをわずかに異なる形式で提供するため)

  3. 拡張メソッドを持つコンバータークラス。この方法で、Consumerクラスの.ToPerson()メソッドを記述でき、独自のNewConsumerクラスで別のAPIが導入された場合、別の拡張メソッドを記述して、すべてを同じファイルに保存できます。拡張メソッドは一般的に悪であり、絶対に必要な場合にのみ使用すべきだという意見を聞いたことがあります。それ以外の場合、私はこのソリューションが好きです

  4. Converter / Mapperクラス。変換を処理する別のクラスを作成し、ソースクラスインスタンスを引数として受け取り、宛先クラスインスタンスを返すメソッドを実装します。

要約すると、私の問題は質問の数に減らすことができます(すべて、上記で説明した内容に関連して)。

  1. 変換メソッドを(POCO?)オブジェクト(Consumerクラスの.ToPerson()メソッドなど)内に配置することは、単一の責任パターンを壊すことを考慮していますか?

  2. (DTOのよ​​うな)クラスでの変換コンストラクターの使用は、単一の責任パターンの破壊と見なされますか?特に、そのようなクラスを複数のソースタイプから変換できる場合、複数の変換コンストラクタが必要になりますか?

  3. 元のクラスのソースコードにアクセスしながら拡張メソッドを使用するのは悪い習慣と見なされていますか?このような動作は、ロジックを分離するための実行可能なパターンとして使用できますか、それともアンチパターンですか?


あるPersonクラスはDTO?動作が含まれていますか?
ヤコブマサド

私の知る限り、技術的にはDTOです。拡張メソッドとして「注入」されたロジックがいくつか含まれていますが、このロジックは「ConvertToThatClass」タイプのメソッドに限定されています。これらはすべて従来の方法です。私の仕事は、これらのクラスの一部を使用する新しい機能を実装することですが、一貫性を保つだけで悪い場合は、現在のアプローチに固執すべきではないと言われました。だから、拡張メソッドを使用して変換するこの既存のアプローチが良いものであるかどうか、それを固執するか、何か他のものを試すべきか疑問に思っています
-emsi

回答:


11

変換メソッドを(POCO?)オブジェクト(Consumerクラスの.ToPerson()メソッドなど)内に配置することは、単一の責任パターンを壊すことを考慮していますか?

はい、変換は別の責任です。

(DTOのよ​​うな)クラスでの変換コンストラクターの使用は、単一の責任パターンの破壊と見なされますか?特に、そのようなクラスを複数のソースタイプから変換できる場合、複数の変換コンストラクタが必要になりますか?

はい、変換は別の責任です。コンストラクターまたは変換メソッド(例ToPerson)を使用して実行しても違いはありません。

元のクラスのソースコードにアクセスしながら拡張メソッドを使用するのは悪い習慣と見なされていますか?

必ずしも。拡張するクラスのソースコードがある場合でも、拡張メソッドを作成できます。拡張メソッドを作成するかどうかは、そのようなメソッドの性質によって決定されるべきだと思います。たとえば、多くのロジックが含まれていますか?オブジェクト自体のメンバー以外のものに依存していますか?動作するために依存関係を必要とする拡張メソッドや、複雑なロジックを含む拡張メソッドを使用するべきではないと言うでしょう。拡張メソッドには、最も単純なロジックのみを含める必要があります。

このような動作は、ロジックを分離するための実行可能なパターンとして使用できますか、それともアンチパターンですか?

ロジックが複雑な場合、拡張メソッドを使用しないでください。前述したように、拡張メソッドは最も単純なものにのみ使用する必要があります。変換は簡単だとは思いません。

変換サービスを作成することをお勧めします。次のように、単一の汎用インターフェースを使用できます。

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

そして、あなたはこのようなコンバーターを持つことができます:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

そして、あなたは使用することができます依存性注入をコンバータ(例えば注入するIConverter<Person,Customer>間に変換する能力が必要ですどのクラスにも)PersonとをCustomer


私が間違っていなければIConverter、フレームワークには既に実装されており、実装されるのを待っています。
ラバーダック

@RubberDuck、それはどこにありますか?
ヤコブマサド

私が考えていたのはIConvertable、ここで探しているものではありません。私の間違い。
ラバーダック

あはは!@YacoubMassadについて考えていたことを見つけました。ConverterはをList呼び出すときに使用されConvertAllます。msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspxこれがOPにどれほど役立つかはわかりません。
ラバーダック

関連性もあります。他の誰かがあなたがここで提案するアプローチを取っています。codereview.stackexchange.com/q/51889/41243
ラバーダック

5

変換メソッドを(POCO?)オブジェクト(.ToPerson()Consumerクラスのメソッドのような)オブジェクト内に配置することは、単一の責任パターンを破ることを考慮していますか?

はい。Consumerクラスは、消費者に関連するデータを保持する(そしておそらくいくつかのアクションを実行)する責任があると変換するための責任を負うべきではない、それ自体を別の、無関係のタイプに。

(DTOのよ​​うな)クラスでの変換コンストラクターの使用は、単一の責任パターンの破壊と見なされますか?特に、そのようなクラスを複数のソースタイプから変換できる場合、複数の変換コンストラクタが必要になりますか?

多分。通常、ドメインとDTOオブジェクトの両方の外側に変換を行うメソッドがあります。私はしばしば、ドメインオブジェクトを取り込み、それらをデータベース、メモリ、ファイルなどにシリアル化するリポジトリを使用します。ドメインロジックにそのロジックを配置すると、テスト(およびその他のもの)に適さない特定の形式のシリアル化に結び付けられます。DTOクラスにロジックを入れると、それらをドメインに結び付け、テストを制限します。

元のクラスのソースコードにアクセスしながら拡張メソッドを使用するのは悪い習慣と見なされていますか?

一般的には使用しませんが、過度に使用することはできます。拡張メソッドでは、オプションを作成しますて、コードを読みやすく拡張機能します。それらには欠点があります-それは、コンパイラが解決しなければならないあいまいさを作成するのが簡単であり(時には静かに)、拡張メソッドがどこから来たのか完全に明らかではないため、コードのデバッグをより困難にする可能性があります。

あなたの場合、コンバーターまたはマッパークラスは、最も単純で最も簡単なアプローチのように見えますが、拡張メソッドを使用して何か間違ったことをしているとは思いません。


2

AutoMapperまたはカスタムマッパーを使用してどうですか

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

ドメインから

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

内部では、AutoMapperを抽象化するか、独自のマッパーをロールすることができます


2

私はこれが古いことを知っていますが、まだ素晴らしいです。これにより、両方の方法を変換できます。これは、Entity Frameworkで作業し、ビューモデル(DTO)を作成するときに役立ちます。

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

AutoMapperは、非常に単純なマッピング操作を行うには少々大変だと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.