質問

次のようなユーザー定義型を定義して使用するロジックがあります。

class Word
{
  System.Drawing.Font font; //a System type
  string text;
}

class Canvass
{
  System.Drawing.Graphics graphics; //another, related System type
  ... and other data members ...
  //a method whose implementation combines the two System types
  internal void draw(Word word, Point point)
  {
    //make the System API call
    graphics.DrawString(word.text, word.font, Brushes.Block, point);
  }
}

タイプを使用して計算を行った後のロジック (例:それぞれを見つけるために Word インスタンス)、間接的にいくつかを使用します System API (たとえば、 Canvass.draw 方法。

このロジックを独立したものにしたいのですが、 System.Drawing 名前空間:主に、単体テストを支援するためです (単体テストの出力は、 draw メソッドは実際以外のものを描画していました System.Drawing.Graphics 実例)。

ロジックの依存関係を排除するには、 System.Drawing 名前空間のプレースホルダーとして機能する新しいインターフェイスをいくつか宣言しようと思いました。 System.Drawing タイプは次のとおりです。

interface IMyFont
{
}

interface IMyGraphics
{
  void drawString(string text, IMyFont font, Point point);
}

class Word
{
  IMyFont font; //no longer depends on System.Drawing.Font
  string text;
}

class Canvass
{
  IMyGraphics graphics;  //no longer depends on System.Drawing.Graphics
  ... and other data ...

  internal void draw(Word word, Point point)
  {
    //use interface method instead of making a direct System API call
    graphics.drawText(word.text, word.font, point);
  }
}

これを行うと、異なるアセンブリには異なる実装が含まれる可能性があります。 IMyFont そして IMyGraphics インターフェイス、たとえば...

class MyFont : IMyFont
{
  System.Drawing.Font theFont;
}

class MyGraphics : IMyGraphics
{
  System.Drawing.Graphics theGraphics;

  public void drawString(string text, IMyFont font, Point point)
  {

    //!!! downcast !!!

    System.Drawing.Font theFont = ((MyFont)font).theFont;

    //make the System API call
    theGraphics.DrawString(word.text, theFont, Brushes.Block, point);
  }
}

...ただし、実装には上で示したようにダウンキャストが必要になります。

私の質問は、 実装でダウンキャストを必要とせずにこれを行う方法はありますか? 「これ」とは、「次のような UDT を定義する」ことを意味します。 Word そして Canvass 特定のコンクリートに依存しない System 種類"?

代わりに抽象 UDT を使用することもできます。

class Word
{
  //System.Drawing.Font font; //declared in a subclass of Word
  string text;
}

class Canvass
{
  //System.Drawing.Graphics graphics; //declared in a subclass of Canvass
  //concrete draw method is defined in a subclass of Canvass
  internal abstract void draw(Word word, Point point); 
}

...しかし、これもサブクラスの実装でダウンキャストが必要になります。

ダブルディスパッチイディオムを使用することも考えましたが、それは API 内のさまざまなサブクラスの名前に依存します。

それとも、インターフェイスやサブクラスを使用しない場合、デリゲートを使用する方法はありますか?


- 編集: -

考えられる答えは 2 つあります。

1 つの答えは、以下の「Sir Lantis」の答えで示唆されているとおり、また John Skeet がリンクしているブログ投稿で示唆されているとおり、ジェネリックを使用することです。これはほとんどのシナリオでうまく機能すると思います。私の観点から見るとマイナス面は、 TFont テンプレートパラメータとして:のようなクラスだけではありません Word (これには、 Font インスタンス)はジェネリッククラスになる必要があります(次のような) WordT<TFont>) ...を含むクラスも同様です WordT<TFont> (例えば。 Paragraph) を使用してジェネリックにする必要もあります。 TFont パラメータ(例: ParagraphT<TFont>)。最終的に、アセンブリ内のほぼすべてのクラスがジェネリック クラスになりました。これ する 型安全性を維持し、ダウンキャストの必要性を回避します... しかし これはちょっと見苦しく、カプセル化の錯覚 (「フォント」が不透明な実装の詳細であるという錯覚) を妨げます。

別の答えは、ユーザー クラスでマップまたは辞書を使用することです。の代わりに Font 再利用可能なライブラリで、抽象インターフェイスの代わりに、次のような「ハンドル」クラスを定義します。

public struct FontHandle
{
  public readonly int handleValue;
  FontHandle(int handleValue)
  {
    this.handleValue = handleValue;
  }
}

そして、そこからダウンキャストするのではなく、 FontHandle, 、保管してください Dictionary<int, Font> マッピングするインスタンス FontHandle 値を Font インスタンス。

役に立ちましたか?

解決

まず、シナリオ全体が少し人工的ではないかと思います。本当に行くつもりですか 必要 このレベルの抽象度?おそらく購読する ヤグニ?

なぜあなたの MyGraphics のみで動作します MyFont?で動作しますか IFont?そうすればインターフェースをより適切に使用でき、この問題全体を回避できるでしょう...

オプションの 1 つは、少し再設計することです。 IFont フォントのメタデータ (サイズ、フォントフェイスなど) を記述するだけで、具体的な内容が得られます。 MyGraphics のように:

[public|internal] MyFont GetFont(IFont font) {...} // or just Font

そして、翻訳を行うのはグラフィックスの仕事になるため、次のようなものを使用します。

public void drawString(string text, IMyFont font, Point point)
{
    using(System.Drawing.Font theFont = GetFont(font))
    {
        theGraphics.DrawString(word.text, theFont, Brushes.Block, point);
    }
    // etc
}

もちろん、 Point 翻訳も必要かもしれません ;-p

他のヒント

あなたは事実上、「私はコンパイラよりもよく知っています - コンパイラが必ずコンパイラのインスタンスになることを知っています」と言っていることになります。 MyFont." その時点で、あなたは MyFont そして MyGraphics 再び密結合されるため、インターフェイスのポイントが少し減ります。

すべき MyGraphics どれでも作業できます IFont, 、またはのみ MyFont?どれでも機能させることができれば、 IFont 大丈夫ですよ。それ以外の場合は、コンパイル時にすべてを型安全にするために、複雑なジェネリックを検討する必要があるかもしれません。見つかるかもしれません プロトコルバッファーのジェネリックに関する私の投稿 同様の状況として役立ちます。

(サイド提案 - メソッドの Pascal ケースを含む命名規則に従うと、コードはより慣用的に .NET に似たものになります。)

私は今のところ、C# についてあまり意識していません。もうしばらく経ちました。ただし、すべての内容をそこにキャストしたくない場合は、ジェネリックスの使用を余儀なくされる可能性があります。

Java コードを提供するだけですが、C# でも同じことができるはずです。 where キーワード。

インターフェースを汎用インターフェースにします。Java では次のようになります

IMyGraphics<T extends IMyFont>その後 MyGraphics : IMyGraphics<MyFont>

次に、再定義します drawString 取る署名 T font の代わりに 2 番目のパラメータとして IMyFont. 。これにより、次のことが可能になります。

public void drawString(string text, MyFont font, Point point)

あなたの中に直接 MyGraphics クラス。


C# では、 IMyGraphics<T extends IMyFont> あるべきです public interface IMyGraphics<T> where T:IMyFont, しかし、私はそれについて100%確信しているわけではありません。

のキャストが気に入らない IFontMyFont?あなたはこれを行うことができます:

interface IFont {
    object Font { get; }
}

class MyFont : IFont {
    object Font { get { return ...; } }
}

確かに、まだキャストする必要があります System.ObjectSystem.Drawing.Font 描画メソッド内ではありますが、特定のクラス実装への依存関係を削除しただけです (MyFont).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top