次のようないくつかのユーザー定義型を定義して使用するロジックがあります。
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
andIMyGraphics
インターフェイスのさまざまな実装が含まれる可能性があります。たとえば...
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
あるのは、(インスタンスを含む)のようなクラスだけではありません...それはまた、(eg )を含むクラスも、パラメーター (eg ) を使用してジェネリックにする必要があります。最終的に、アセンブリ内のほぼすべてのクラスがジェネリック クラスになりました。これにより、タイプセーフが維持され、ダウンキャストの必要がなくなります...しかしFont
WordT<TFont>
WordT<TFont>
Paragraph
TFont
ParagraphT<TFont>
それは一種の醜いものであり、カプセル化の錯覚 (「フォント」が不透明な実装の詳細であるという錯覚) を妨げます。
もう 1 つの答えは、ユーザー クラスでマップまたは辞書を使用することです。再利用可能なライブラリの代わりFont
に、抽象インターフェイスの代わりに、次のような「ハンドル」クラスを定義します。
public struct FontHandle
{
public readonly int handleValue;
FontHandle(int handleValue)
{
this.handleValue = handleValue;
}
}
次に、からダウンキャストする代わりに、値をインスタンスにマップFontHandle
するインスタンスを保持します。Dictionary<int, Font>
FontHandle
Font