タイプのマッピングとC#での拡張メソッドの使用に関するベストプラクティスについていくつか質問したいと思います。このトピックが過去数年にわたって何度も議論されたことは知っていますが、私は多くの投稿を読んだことがあり、それでも疑問があります。
私が遭遇した問題は、私が「変換」機能で所有しているクラスを拡張することでした。いくつかのロジックで使用されるオブジェクトを表すクラス「Person」があるとします。また、外部APIからの応答を表す "Customer"クラスもあります(実際には複数のAPIがあるため、各APIの応答を共通の型であるPersonにマップする必要があります)。私は両方のクラスのソースコードにアクセスでき、理論的には自分のメソッドをそこで実装できます。データベースに保存できるように、顧客を個人に変換する必要があります。プロジェクトは自動マッパーを使用しません。
私は4つの可能な解決策を心に留めています:
Consumerクラスの.ToPerson()メソッド。シンプルですが、単一の責任のパターンを壊しているように見えます。特に、Consumerクラスが他のクラス(別の外部APIで必要なクラス)にもマップされているため、複数のマッピングメソッドを含める必要があります。
引数としてコンシューマーを取るPersonクラスのマッピングコンストラクター。また、簡単で、単一責任パターンを破るようにも見えます。複数のマッピングコンストラクターが必要です(別のAPIのクラスがあり、コンシューマーと同じデータを提供しますが、形式が少し異なります)。
拡張メソッドを持つConvertersクラス。このように、Consumerクラスの.ToPerson()メソッドを記述できます。また、独自のNewConsumerクラスを使用して別のAPIが導入された場合、別の拡張メソッドを記述して、すべてを同じファイルに保持できます。拡張メソッドは一般に悪であり、どうしても必要な場合にのみ使用する必要があるという意見を聞いたことがあります。そうでなければ、私はこの解決策が好きです
Converter/Mapperクラス。変換を処理する別のクラスを作成し、ソースクラスインスタンスを引数として取り、宛先クラスインスタンスを返すメソッドを実装します。
要約すると、私の問題は質問の数に減らすことができます(すべて上記で説明した内容に関連して)。
変換メソッドを(POCO?)オブジェクト内(コンシューマクラスの.ToPerson()メソッドなど)に配置すると、単一の責任パターンが壊れると考えられますか?
(DTOのような)クラスで変換コンストラクターを使用すると、単一の責任パターンが壊れると考えられますか?特にそのようなクラスが複数のソースタイプから変換できる場合、複数の変換コンストラクターが必要になりますか?
元のクラスのソースコードにアクセスしながら拡張メソッドを使用することは悪い習慣と見なされていますか?そのような動作は、ロジックを分離するための実行可能なパターンとして使用できますか、それともアンチパターンですか?
変換メソッドを(POCO?)オブジェクト内(コンシューマクラスの.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
}
}
そして、あなたは Dependency Injection を使ってコンバーターを注入することができます(例えばIConverter<Person,Customer>
)をPerson
とCustomer
の間で変換する機能を必要とするクラスに。
(POCO?)オブジェクト(Consumerクラスの
.ToPerson()
メソッドのような)内に変換メソッドを置くことは、単一の責任パターンを壊すことと考えられていますか?
はい。 Consumer
クラスは、コンシューマーに関連するデータを保持する(および場合によってはいくつかのアクションを実行する)責任があり、itselfを別の無関係な型に変換する責任はありません。
(DTOのような)クラスで変換コンストラクターを使用すると、単一の責任パターンが壊れると考えられますか?特にそのようなクラスが複数のソースタイプから変換できる場合、複数の変換コンストラクターが必要になりますか?
多分。私は通常、変換を行うために、ドメインとDTOオブジェクトの両方の外部にメソッドを持っています。私はよくドメインオブジェクトを取り込み、データベース、メモリ、ファイルなど、それらに(非)シリアル化するリポジトリを使用します。このロジックをドメインクラスに配置すると、特定の形式のシリアル化に結び付けられます。これは、テスト(およびその他のこと)には不向きです。ロジックをDTOクラスに配置した場合、それらをドメインに結び付け、テストを制限しました。
元のクラスのソースコードにアクセスしながら拡張メソッドを使用することは悪い習慣と見なされていますか?
一般的ではありませんが、canは使いすぎます。拡張メソッドを使用して、コードを読みやすくするオプション拡張を作成します。それらには欠点があります-それは、コンパイラーが解決する必要があるあいまいさを作成するのが簡単で(時々静かに)、拡張メソッドがどこから来ているのか完全に明らかではないため、コードのデバッグをより困難にする可能性があります。
あなたの場合、拡張メソッドを使用して何かをしているとは思いませんが、コンバーターまたはマッパークラスは最も単純で最も単純なアプローチのように見えますwrong.
AutoMapperまたはカスタムマッパーを使用するのはどうですか?
MyMapper
.CreateMap<Person>()
.To<PersonViewModel>()
.Map(p => p.Name, vm => vm.FirstName)
.To<SomeDTO>()
.Map(...);
ドメインから
db.Persons
.ToListAsync()
.Map<PersonViewModel>();
内部でAutoMapperを抽象化するか、独自のマッパーをロールすることができます
私はこれが古いことを知っていますが、それでもなお素晴らしいです。これにより、両方の方法に変換できます。これは、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は、非常に単純なマッピング操作を実行するのに少し役立ちます。