web-dev-qa-db-ja.com

オブジェクトグラフの作成を作成/非表示にするためのC#パターン

オブジェクトグラフの作成を非表示にするには、どのようなアプローチが良いのかについてアドバイスを受けたいと思います。以下のコードでは、SomeClassがThingオブジェクトグラフ全体(CreateThingメソッド内のすべてのオブジェクト)の作成に直接関与しないようにしたいと思います。 Thingグラフ内のオブジェクトの数(少しのコンテキストでは、すべてWCFオブジェクトです)

さらに、Thingオブジェクトグラフのプロパティを初期化するためだけにIConfigServiceSomeClassに渡されるコード臭いがあるように感じます。 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依存関係を持たせる必要がありますか?これ自体は適切な工場のようには見えません。考え?

2
AvacadoRancher

SomeClassThingインスタンスの作成を担当しないようにするには、担当するものを指定します。

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
    }
}
2
David Arno

私があなたの要件を理解していると仮定します。つまり、オブジェクトの作成の制御を他の場所に委任したいとします。

いくつかのオプションがあります:

  1. Builderパターン を使用します

これはおそらく、これを処理する最もクリーンな方法です(上記のコードに基づく)。オブジェクトがどのように構築されるかを制御するために必要なメソッドを実装できます。必要な場合、またはデフォルトのオブジェクト構造を構築する基本的な「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
    }

}
  1. ファクトリパターン を使用します

過度に複雑なオブジェクトを構築したくなく、基本クラスまたはインターフェースの複数の実装を提供したい場合は、ファクトリーパターンが最善の策です。

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
    }
}
  1. 戦略パターン を使用します

いくつかの条件に基づいて異なる具体的なオブジェクトが必要ですか?その場合、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
    }
}

どちらを使用するかは、特定の要件によって異なります。

これがお役に立てば幸いです。

0
Toni Kostelac

まず、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を使用しないでください。代わりに独自のモデルを使用してください。サービスを注入することよりも、デカップリングの方が重要です。

0
Engineert

SomeClassThingのような名前の場合、何がどこにあるのかについての意味的な手がかりはありません。

それは依存関係の分析を残します。 CreateThingにはbarconfigService以外は必要ないようです。 configServiceCreateThingSomeClassから取得するすべてです。 configService(またはProp1と言うと、SomeClassCreateThingを分離したい場合は、直接渡すことができます。別の時間に設定する必要がある場合は、メソッドまたはそれを保持する新しいタイプのオブジェクトに渡すことができます。

そのいずれかを行うのが理にかなっているかどうか、私はこのような名前の手がかりはありません。しかし、はい、「抽出メソッド」のようなリファクタリングが存在するという事実は、本当に必要な場合にこのようなことを実行できることを証明します。

CreateThingを新しい型、たとえばThingFactoryに移動したら、その型をSomeClassの一部にして、そのCreateThingメソッドをいつでも呼び出すことができます。 IThingFactoryを使用してこれを実行すると、 Abstract Factory Pattern の基礎が整いました。つまり、SomeClassを変更することなく、ThingFactoryのさまざまな種類のThingsを渡して、さまざまな種類のSomeClassグラフを取得できます。

それがあなたに役立つかどうかはわかりませんが、それはあなたを隅に描くことなくあなたが求めていることを行います。

0
candied_orange