2

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

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);
  }
}

これを行った場合、さまざまなアセンブリにIMyFontandIMyGraphicsインターフェイスのさまざまな実装が含まれる可能性があります。たとえば...

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 を定義する」ことを意味WordCanvassます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あるのは、(インスタンスを含む)のようなクラスだけではありません...それはまた、(eg )を含むクラスも、パラメーター (eg ) を使用してジェネリックにする必要があります。最終的に、アセンブリ内のほぼすべてのクラスがジェネリック クラスになりました。これにより、タイプセーフ維持され、ダウンキャストの必要がなくなります...しかしFontWordT<TFont>WordT<TFont>ParagraphTFontParagraphT<TFont>それは一種の醜いものであり、カプセル化の錯覚 (「フォント」が不透明な実装の詳細であるという錯覚) を妨げます。

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

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

次に、からダウンキャストする代わりに、値をインスタンスにマップFontHandleするインスタンスを保持します。Dictionary<int, Font>FontHandleFont

4

4 に答える 4

2

まず、シナリオ全体が少し人工的ではないかと思います。このレベルの抽象化が本当に必要ですか? おそらくYAGNIを購読しますか?

なぜあなただ​​けがMyGraphicsで動作するのMyFontですか?で動作しIFontますか?それはインターフェースのより良い使用法であり、この問題全体を回避するでしょう...

IFont1つのオプションは、フォントのメタデータ(サイズ、フォントフェイスなど)を記述するだけで、具体的には次の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

于 2009-02-02T13:12:59.713 に答える
2

あなたは効果的に「私はコンパイラよりもよく知っていますMyFont. その時点で、再び密結合になり、インターフェイスのポイントが少し減りますMyFontMyGraphics

MyGraphics任意IFontの 、または ? のみで動作する必要がありMyFontます。どれでも動作させることができればIFont大丈夫です。それ以外の場合は、複雑なジェネリックを調べて、すべてをコンパイル時のタイプ セーフにする必要があります。同様の状況として、Protocol Buffers のジェネリックに関する私の投稿が役立つ場合があります。

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

于 2009-02-02T13:14:02.083 に答える
1

私は現在、C# についてあまり認識していません。しかし、すべてのものをそこにキャストしたくない場合は、ジェネリックの使用を余儀なくされる可能性があります。

where私は Java コードを提供することしかできませんが、C# はキーワードを介して同じことができるはずです。

インターフェイスを汎用インターフェイスにします。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.

于 2009-02-02T13:38:48.773 に答える
0

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

interface IFont {
    object Font { get; }
}

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

System.Objectもちろん、描画メソッドで from からtoにキャストする必要がありSystem.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);
}
于 2009-02-02T13:27:40.637 に答える