オブジェクトグラフの作成を非表示にするには、どのようなアプローチが良いのかについてアドバイスを受けたいと思います。以下のコードでは、SomeClass
がThingオブジェクトグラフ全体(CreateThing
メソッド内のすべてのオブジェクト)の作成に直接関与しないようにしたいと思います。 Thing
グラフ内のオブジェクトの数(少しのコンテキストでは、すべてWCFオブジェクトです)
さらに、Thingオブジェクトグラフのプロパティを初期化するためだけにIConfigService
がSomeClass
に渡されるコード臭いがあるように感じます。 IConfigService
依存関係をこのクラスから直接削除したいのですが、このクラスには直接関係がないようです。
public class SomeClass : ISomeClass
{
private readonly IFirstDependency _firstDependency;
private readonly ISecondDependency _secondDependency;
private readonly IConfigService _configService;
public SomeClass(IFirstDependency, firstDependency, ISecondDependency secondDependency, IConfigService configService)
{
_firstDependency = firstDependency;
_secondDependency = secondDependency;
_configService = configService;
}
public List<Foo> GetFoo(BarDto bar)
{
Thing thing = CreateThing(bar);
/// other code omitted
}
private Thing CreateThing(BarDto bar)
{
return new Thing()
{
Child = new Child()
{
SimpleProp = "simple",
GrandChild = new GrandChild()
{
Items = new List<Item>()
{
Item = new Item()
{
Prop1 = _configService.GetSettings...
Prop2 = $"{bar.This} {bar.That}",
Prop3 = $"{bar.MoreBar} {bar.EvenMoreBar}"
}
}
}
}
};
}
}
オブジェクトグラフの作成をインターフェイスの背後に隠して、実装にIConfigService
依存関係を持たせる必要がありますか?これ自体は適切な工場のようには見えません。考え?
SomeClass
がThing
インスタンスの作成を担当しないようにするには、担当するものを指定します。
public class SomeClass : ISomeClass
{
private readonly IFirstDependency _firstDependency;
private readonly ISecondDependency _secondDependency;
private readonly Func<Thing, BarDto> _thingCreator;
public SomeClass(IFirstDependency firstDependency,
ISecondDependency secondDependency,
Func<Thing, BarDto> thingCreator)
{
_firstDependency = firstDependency;
_secondDependency = secondDependency;
_thingCreator = thingCreator;
}
public List<Foo> GetFoo(BarDto bar)
{
var thing = _thingCreator(bar);
/// other code omitted
}
}
そして、インジェクション機能をサポートできないDIコンテナーを使用している場合は、そのDIコンテナーの使用を停止して純粋なDIを使用するか、インターフェース/クラスにラップしてコンテナーを支援します。
public ThingCreater : IThingCreator
{
public Thing CreateThing(BarDto bar)
{
...
}
}
public class SomeClass : ISomeClass
{
private readonly IFirstDependency _firstDependency;
private readonly ISecondDependency _secondDependency;
private readonly IThingCreator _thingCreator;
public SomeClass(IFirstDependency firstDependency,
ISecondDependency secondDependency,
IThingCreator thingCreator)
{
_firstDependency = firstDependency;
_secondDependency = secondDependency;
_thingCreator = thingCreator;
}
public List<Foo> GetFoo(BarDto bar)
{
var thing = _thingCreator.CreateThing(bar);
/// other code omitted
}
}
私があなたの要件を理解していると仮定します。つまり、オブジェクトの作成の制御を他の場所に委任したいとします。
いくつかのオプションがあります:
これはおそらく、これを処理する最もクリーンな方法です(上記のコードに基づく)。オブジェクトがどのように構築されるかを制御するために必要なメソッドを実装できます。必要な場合、またはデフォルトのオブジェクト構造を構築する基本的な「Build」メソッドのみを使用できます。
ビルダーパターンは、複雑なオブジェクトの作成を抽象化したい場合に最適です。
例:
public interface IThingBuilder
{
Thing Build(BarDto dto);
}
public class ThingBuilder
{
public Thing Build(BarDto dto)
{
//... insert your complex object construction process here
}
}
public class SomeClass
{
private IThingBuilder _thingBuilder;
public SomeClass(IThingBuilder thingBuilder)
{
_thingBuilder = thingBuilder;
}
public List<Foo> GetFoo(BarDto bar)
{
Thing thing = _thingBuilder.Build(bar);
/// other code omitted
}
}
過度に複雑なオブジェクトを構築したくなく、基本クラスまたはインターフェースの複数の実装を提供したい場合は、ファクトリーパターンが最善の策です。
public interface IThingFactory
{
public Thing GetThing(string type, BarDto dto);
}
public class ThingFactory : IThingFactory
{
public Thing GetThing(string type, BarDto dto)
{
switch(type)
{
case "SomeThing": return new SomeThing(dto);
case "SomeOtherThing": return new SomeOtherThing(dto);
default: return new Thing(dto);
}
}
}
public class SomeClass
{
private IThingFactory _thingFactory;
public SomeClass(IThingFactory thingFactory)
{
_thingFactory = thingFactory;
}
public List<Foo> GetFoo(BarDto bar)
{
var thing = _thingFactory.GetThing("SomeThing", bar);
/// other code omitted
}
}
いくつかの条件に基づいて異なる具体的なオブジェクトが必要ですか?その場合、Factoryパターンをswitch-caseまたはif-elseステートメントと組み合わせると、既知のインターフェース/基本クラスを満たすさまざまな具象型のインスタンスを柔軟に作成できます。
public interface IThingFactory
{
public Thing GetThing(string type, BarDto dto);
}
public class ThingFactory : IThingFactory
{
public Thing GetThing(string type, BarDto dto)
{
switch(type)
{
case "SomeThing": return new SomeThing(dto);
case "SomeOtherThing": return new SomeOtherThing(dto);
default: return new Thing(dto);
}
}
}
public class SomeClass
{
private IThingFactory _thingFactory;
public SomeClass(IThingFactory thingFactory)
{
_thingFactory = thingFactory;
}
public List<Foo> GetFoo(BarDto bar)
{
Thing thing;
if(string.IsNullOrEmpty(bar.PropertyA))
{
thing = _thingFactory.GetThing("SomeOtherThing", bar);
}
else
{
thing = _thingFactory.GetThing("SomeThing", bar);
}
/// other code omitted
}
}
どちらを使用するかは、特定の要件によって異なります。
これがお役に立てば幸いです。
まず、CreateThing
で問題が発生しています。私が見るように、このメソッドは新しいChild
クラスを作成する責任があります。その後、Thing
クラス全体を作成する必要はありません。これを担当者のクラスに任せます。
Thing
クラスを作成して初期化する必要がある場合でも、静的ヘルパークラスを作成するか、初期化する必要のあるコンストラクタをThing
クラスに追加できます。
public class Thing
{
private Child _child;
public Thing(Child child)
{
_child = child;
}
}
さらに、List<Foo>
を取得することで多くのビジネスを行う必要がある場合は、懸念事項(ビジネス)をさまざまなクラスに分離し、そこから必要なオブジェクトを取得できます。次に、Thing
オブジェクトを初期化します。
public List<Foo> GetFoo(BarDto bar)
{
Child child = _service1.GetChild(bar);
Child2 child2 = _service2.GetChild(bar);
Thing thing = new Thing(child, child2);
//..
}
さらに、Thingオブジェクトグラフのプロパティを初期化するためだけにIConfigServiceがSomeClassに渡されるコード臭いがあるように感じます。 IConfigService依存関係はこのクラスの直接の問題ではないようなので、このクラスから削除します
あなたのビジネスを行うにはIConfigService
が必要です。このインターフェースを注入することは、すぐに初期化するよりも本当に良い方法です。あなたは元気ですこれをこのメソッドでのみ使用しているため、コンストラクタにIConfigService
を挿入することに不満があると思います。その場合、IoCコンテナを使用すると、IConfigService
を挿入する新しいプロパティを作成できます。
private IConfigService ConfigService
{
get
{
return Container.Context.GetInstance<IConfigService>()
}
}
このようにすることで、ConfigService
プロパティを呼び出すときにのみ初期化します。しかし、それは大したことではなく、この方法ですべての注入を実装する必要はありません。この方法はコンテナに依存するためです。
ところで、SomeClass
がビジネスクラス(サービスレイヤークラスではない)の場合、Busines Logic Layer(BLL)でDTOを使用しないでください。代わりに独自のモデルを使用してください。サービスを注入することよりも、デカップリングの方が重要です。
SomeClass
やThing
のような名前の場合、何がどこにあるのかについての意味的な手がかりはありません。
それは依存関係の分析を残します。 CreateThing
にはbar
とconfigService
以外は必要ないようです。 configService
はCreateThing
がSomeClass
から取得するすべてです。 configService
(またはProp1
と言うと、SomeClass
とCreateThing
を分離したい場合は、直接渡すことができます。別の時間に設定する必要がある場合は、メソッドまたはそれを保持する新しいタイプのオブジェクトに渡すことができます。
そのいずれかを行うのが理にかなっているかどうか、私はこのような名前の手がかりはありません。しかし、はい、「抽出メソッド」のようなリファクタリングが存在するという事実は、本当に必要な場合にこのようなことを実行できることを証明します。
CreateThing
を新しい型、たとえばThingFactory
に移動したら、その型をSomeClass
の一部にして、そのCreateThing
メソッドをいつでも呼び出すことができます。 IThingFactory
を使用してこれを実行すると、 Abstract Factory Pattern の基礎が整いました。つまり、SomeClass
を変更することなく、ThingFactory
のさまざまな種類のThing
sを渡して、さまざまな種類のSomeClass
グラフを取得できます。
それがあなたに役立つかどうかはわかりませんが、それはあなたを隅に描くことなくあなたが求めていることを行います。