私が次の基本クラスを持っていると仮定しましょう:
_public class Base
{
public int Id {get; set;}
public string SomeText {get; set;}
public string SomeOtherText {get; set;}
public static Base BuildFromOtherAssembly(ClassFromAssemblyA source)
{
Id = source.Id;
SomeText = source.SomeText;
SomeOtherText = source.SomeOtherText
}
}
public class Derived : Base
{
public DataFromAssemblyB DataFromAssemblyB {get; set;}
public Derived(DataFromAssemblyB dataFromAssemblyB)
{
DataFromAssemblyB = dataFromAssemblyB;
}
}
_
基本クラスは、AssemblyAを参照するAssemblyからビルドされ、AssemblyAについて何も知らないAssemblyBに返されます。
AssemblyBは、独自のデータをBaseクラスに追加し、戻り値の型が追加のデータが存在することを示していることを確認する必要があります。
基本的に、DataFromAssemblyBが存在することをコンパイラレベルで確認したいと思います。
このシナリオではコンポジションがうまく機能することは知っていますが、問題は論理的にはDataFromAssemblyBがBaseに属していることです。
より現実的な例を示すために、次のシナリオを想像してください。
_public class ShoppingCart
{
public int Id {get; set;}
public string OwnerName {get; set;}
public decimal Value {get; set;}
public static Base BuildFromDataModel(ShoppingCartDatabaseModel source)
{
Id = source.Id;
OwnerName = source.OwnerName;
Value = source.Value
}
}
public class ShoppingCartWithContents : ShoppingCart
{
public ShoppingCartContent Content {get; set;}
public ShoppingCartWithContents (ShoppingCartContent content)
{
Content = content;
}
}
_
これで説明できるといいのですが。この問題が発生する理由は、ShoppingCartがデータベースレイヤーから取得され、ShoppingCartContentがキャッシュレイヤーから取得され、中間レイヤーがデータを結合しているためです。
強力なタイピングが欲しいので、コンテンツが読み込まれているShoppingCartを扱っているのか、そうではないShoppingCartを扱っているのか、開発者には常に明確です。
開発者がShoppingCartにプロパティを追加することを決定したが、ShoppingCartWithContentsでそのプロパティをマッピングするのを忘れた場合にバグが発生する可能性があるため、手作業ですべてのプロパティをマッピングすることは避けたいです。
このような状況でのベストプラクティスを教えてください。
更新:
私はGithubで思いついたいくつかの異なるバリエーションを作成しました。いくつかのフィードバックや考えを得たいと思います。
前提は同じです。モックデータベースモデルからショッピングカートオブジェクトを作成し、コンテンツデータを追加します。
さまざまな機能をテストするために、ShoppingCart
にShoppingCartWithContents
をきれいに作成できる.AddContents(content)
メソッドがあると便利だと思いました。
ビジネスロジックの代用としての追加の機能テストとして、GetFriendlyString
メソッドを追加しました。このメソッドは、アイテムが追加された後もアクセスできるはずです。
いくつかのサンプルでは、基本的な機能を拡張できることを示すためにGetFriendlyString
を上書きして「アイテムあり」を追加しています。
さまざまなアプローチについて自分の気持ちを上にまとめてみました。
更新2:
私は状況についてもう少し情報を提供したかったのです。それが最善の方法に影響を与える可能性があるからです。
これは、ORMとしてEFを使用したWeb APIプロジェクトです。 ShoppingCart
のような私のビジネスオブジェクトは、EFのDbモデルからハンドマップされ、ビジネスタスクに関連するプロパティのみを選択します。したがって、私のビジネスロジックはORMまたはデータレイヤーによって制約されません(実際、ShoppingCart
を含むアセンブリはEFを参照しません)。
更新と作成は独自のビジネスモデルを通じて処理されます。たとえば、ショッピングカートを更新する必要がある場合、検証ルールがあり、データベースモデルへの変更のマッピング方法のみを知っているShoppingCartUpdate
クラスがあります。
一部のデータはRedisにキャッシュされ、状況によってはEFとRedisの両方からビジネスオブジェクトを作成する必要があります。たとえば、ショッピングカートの例では、一部のビジネスロジックは、ユーザーがショッピングカートを持っているかどうかを知るだけでよい場合がありますが、価格を合計する必要がある他のビジネスロジックは、利用可能な追加情報に依存しています。
HasDataLoaded
のようなブール値の使用は避けたいです。また、コードベース全体でnullチェックとifステートメントが繰り返し発生するため、null許容型も避けたいです。
さらに悪いことに、開発者がif/nullチェックを忘れた場合、空の注文またはそのような愚かなものを発送しようとする可能性があります。
そのため、一意のクラスを使用してデータの存在または不在を強制する方が良いアプローチだと思いました。チェックアウトメソッドはShoppingCartWithContents
パラメータのみを取得するため、コンテンツが確実に読み込まれます。
私はそれが理にかなっていると思います。
要点を見ると、特にShoppingCartのプロパティのみを考慮するメソッドでShoppingCartWithContentsのインスタンスを処理できるようにしたい場合は、通常の古い継承でうまくいくようです。
このような状況でのベストプラクティスを教えてください。
「theベストプラクティス」についてはわかりませんが、 state design pattern のように聞こえます。リンクからの引用:
意図
内部状態が変化したときに、オブジェクトの動作を変更できるようにします。オブジェクトはクラスを変更するように見えます。
オブジェクト指向の状態機械
ラッパー+ポリモーフィックラピー+コラボレーション
状態パターンは、状態変化に応じた多態性動作変化であることがわかります。それは単に構成対継承の議論の解決ではありません。
ただし、パターン(すべてのパターン)はゴスペルではなくガイダンスなので、バリエーションが「theベストプラクティス」から逸脱しているとは言えません。
基本的に、DataFromAssemblyBが存在することをコンパイラレベルで確認したいと思います。
基本クラスはabstract
です。 Ithinkパターンは、ShoppingCart
とShoppingCartWithContent
の両方がこのベースから継承することを示唆しています。コンストラクターパラメーター間で、宣言されたabstract
およびvirtual
メンバー(C#言語)は、懸念事項がカバーされているように聞こえます。
開発者がShoppingCartにプロパティを追加することを決定したが、ShoppingCartWithContentsでそのプロパティをマッピングするのを忘れた場合にバグが発生する可能性があるため、手作業ですべてのプロパティをマッピングすることは避けたいです。
おそらく、マッピング- AutoMapper solution by @tmack など-は、コンストラクタでトリガーするためのベースにデフォルトコードがあるShoppingCartWithContents
にあります。これはちょっと概念的には ビジターパターン のように聞こえますが、少なくとも概念的には(AutoMapperがそれをどのように行うかわかりません)。