web-dev-qa-db-ja.com

相互に依存するサードパーティのオブジェクトのラップとベストプラクティス

私はここで優柔不断の少しの瞬間を持っています、そしてそれについていくつかの視点をお願いします。

私は現在、サードパーティのAPIからオブジェクトをラップしています(自宅で自分のプロジェクトのために、そして仕事中に)、私はできる限りSOLID原則に準拠するように最善を尽くしていますこれはすべてC#を使用して記述されています。ここで明確にしておくと、これはビジネスアプリケーションではなく、サードパーティAPIのより複雑な詳細をラップする再利用のためのフレームワークを作成しています。

だから私の問題はこれです:私はこれらのオブジェクトを私の具体的なインスタンスにラップしています。 nAとnBでラップされたネイティブオブジェクト、およびそれぞれnAとnBを保持するラッパーAとBを呼び出します。

class A { private nA _nA }
class B { private nB _nB }

nAへの参照を渡さないと、nBを作成できません。したがって、nAオブジェクトが公開されないようにするために、ラッパーBを作成するファクトリメソッドがあり、nAをnBのコンストラクターに渡すことができます。だから、最初の質問:これは適切な練習だと思いますか?私がそれを手伝うことができれば、大量のファクトリーやファクトリーメソッドで終わりたくありません(まとめることはかなりあります)。

さて、これはすべてうまくいきます。以上で問題なくAとBが作れます。しかし、今私は別のしわを持っています。 M1(nB)と呼ばれるメソッドをラップする必要があります。パラメーターはタイプBであり、Bラッパー内に格納されています。

public class A { private nA _nA; public M1(B) { _na.M1( ??? );} }

ただし、nBにはアクセスできません。では、ラッパーAに格納されているnA.M1()メソッドにnBを渡すためのベストプラクティスは何ですか?

具体的な型の使用を強制し、nBをラッパーBの内部メンバーにすることができます。

public class B { internal nB { get; private set; } }

しかし、これは密結合をもたらします。とにかくnAとnBが密結合しているので、それは重要ですか?

この種の問題は常に頭痛の種になるので、誰かが助言を惜しまなければ、うんざりするでしょう。

また、SOLIDを可能な限り遵守するよう努めていますが、それらはガイドラインであることを認識しています。結局、ハードルールや宗教ではないので、破壊するSOLID私が必要とするものを得るための原則、そうであること。

私の問題をさらに説明するいくつかのコード:

interface IGraphics
{
   CopyResource(Texture src, Texture dest);
}

interface ITexture
{
   // Stuff for textures that have no bearing on this example.
}

class Graphics
   : IGraphics
{
   // Native object (or object that wraps a native object).
   private ID3D11Context1 _d3dContext;

   public CopyResource(Texture src, Texture dest)
   {
      // Do validation, etc...    

      // How can I do this with interfaces?  
      // Internals are not allowed on interfaces by the language
      _d3dContext.CopyResource(src._d3dTexture, dest._d3dTexture);
   }
}

class Texture
   : ITexture
{
   // Native object (or object that wraps a native object).
   private ID3D11Texture2D _d3dTexture;
   // Other stuff
}
2
Mike

しかし、これは密結合をもたらします。とにかくnAとnBが密結合しているので、それは重要ですか?

いいえ、インターフェースを介してパブリックにアクセスされる内部クラスである限り、問題ではありません。それはクラスの内部で行われているようなものです。FooがプライベートメソッドBarを呼び出すと、Barと密結合されます。ただし、後でその結合を解除し、FooBarおよびBazを呼び出すように変更しても、結合はクラス内にカプセル化されているため、他に影響はありません。

ABの内部にも同じことが当てはまります。 publicメソッドがIAIBのみを参照する限り、ABの内部はnAと自由に絡み合うことができますおよびnB。実際、彼らはこれを自由に行えるだけでなく、次のことを期待されています。それはラッパーとしての彼らの仕事です。

これらのラッパーがインターフェースIAおよびIBを介してシステムの残りの部分に公開されていることを確認してください。そうすることで、必要に応じてラッパー(およびサードパーティのライブラリ)をモックできます。それを行わない場合、ラッパーは何の役にも立ちません。残りのコードは、抽象化レイヤーではなく難読化レイヤーを介してnAnBに結合されているためです。 。

更新

更新されたコードの例を見て、インターフェースの価値よりもインターフェースのほうが問題になる可能性があるため、少し前に戻ります。このようなクラスをラップするとき、私は個人的に次の「ルール」に従います。

  1. ラッパーの背後にあるすべてのデータ型などをカプセル化して、1つのインターフェースだけですべてのラップされたクラスを公開するようにしてください。これは理想的な状況ですが、実用的であることはめったにありません。ここでは機能しません。
  2. たとえば、あなたの場合、ITextureが、Texture(したがってID3D11Texture2DITextureの実装から、次のようなことができるようになります。

    public CopyResource(ITexture src, ITexture dest)
    {
        // Do validation, etc...    
    
       _d3dContext.CopyResource(CreateRealTexture(src).D3dTexture,
                                CreateRealTexture(dest).D3dTexture);
    }
    

    D3dTextureは内部プロパティです。

    これにより、実際のデータタイプをすべて非表示にして、インターフェイス経由でのみ公開することができます。これの欠点は、インターフェイスが複雑になる可能性があり、「実際の」タイプをその場で作成するときに大きなオーバーヘッドが発生することです。ここでも、これは役に立たないでしょう。

  3. 必要に応じて、インターフェイスを廃止し、ファクトリを使用してインスタンスを作成します。しかし、この時点で、私は立ち止まって「なぜですか」と自問します。そのようなラッパーのポイントは何ですか?それらは作成および保守する多くの作業です。それらはテストやDIなどにはあまり役立ちません(インターフェースが不足しているため)。

    それらを作成する理由が「抽象化をアクセスに追加したので、グラフィックライブラリを別のライブラリに交換できる」ためである場合。次に、あなたの時間を無駄にしないでください。別のグラフィックスライブラリがラッパークラスの背後に配置され、それらのラッパーに変更を加えられない可能性はほとんどありません。ライブラリに直接アクセスし、変更した場合は書き直してください。

したがって、ここには完璧な解決策はありません。ラッパーが本当に価値があるかどうかを自問してください。もしそうなら、この特定のケースでの私の腸の反応は、以下のようなものを変更することです:

private ID3D11Context1 _d3dContext;

internalアイテムになる。このようにして、バックグラウンドでラッパークラスがすべての基になる型に簡単にアクセスできます。

1
David Arno

OPの3つの考え。

貧血クラスを使用する

Bが単なるDTOである場合、それをモックできる理由はなく、インターフェースは必要ありません(単体テストはnewを使用してモックデータを入れることができます)。たとえば、Bが日付/時刻スタンプを保持するように設計されたカスタム構造であり、動作なしでフィールドとプロパティのみが含まれている場合は、先に進んでclass Bではなくinterface IBとして公開します。ライブラリからすべての単一のインスタンスを取得する代わりに、クライアントがnewを使用しても問題ありません。クラスがダムデータコンテナーである限り、ユニットテストでも同じことができます。

IDを使用して元のオブジェクトを再構築する

ラッピングしているオブジェクトが使用するすべてのパターンで、like-for-likeを維持する必要はありません。ラッパーのポイントは、クライアントが必要とするものだけを、使いやすく、誤用が困難または不可能な方法で公開することです。状況が変わっても大丈夫です。

たとえば、オブジェクト自体よりも、オブジェクト識別子を受け入れるメソッドを公開する方が理にかなっている場合があります。だから代わりに

void Method1(ICustomer customer);

あなたが公開することができます

void Method1(int customerID);

これで内部的に、method1はそのcustomerIDを使用して、ラップしようとしているライブラリと互換性のある(内部)Customerオブジェクトを構築する必要があります。私はそれがその場でそれを構築できることをお勧めします。

void Method1(int customerID)
{
    var c = internalLibrary.GetCustomer(customerID);
    c.InternalLibraryCall();
}

内部インターフェースを使用する

内部インターフェースという強力なツールを自由に利用できます。内部インターフェースではありませんmembers(あなたが発見したように、これらは許可されていません)がインターフェース自体です。

public interface ICustomer
{
    int CustomerID { get; }
}

internal interface ICustomerEx
{
    InternalCustomer GetOriginalCustomerObject();
}

public class Customer: ICustomer, ICustomerEx  //Client won't see ICustomerEx
{
    InternalCustomer _customer;

    internal Customer(InternalCustomer c)
    {
        _customer = c;
    }

    InternalCustomer ICustomerEx.GetOriginalCustomerObject()
    {
        return _customer;
    }
}

その場合、メソッドは次のようになります。

public void HandleCustomer(ICustomer customer)
{
    var c = customer as ICustomerEx;
    var internalC = c.GetOriginalCustomerObject();
    c.InternalLibraryCall();
}
0
John Wu