我有一些逻辑,它定义和使用一些用户定义的类型,比如:
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);
}
}
...但是,如上图所示,实现将需要向下转换。
我的问题是,有没有一种方法可以做到这一点而无需在实施过程中感到沮丧?Word
通过“这个”,我的意思是“定义像并且Canvass
不依赖于特定具体类型的UDT 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 中的各种子类。
或者,如果没有接口或子类,有没有使用委托的方法?
- 编辑: -
有两种可能的答案。
一个答案是使用泛型,正如下面“兰蒂斯爵士”的回答所建议的那样,以及 John Skeet 链接到的博客文章所建议的那样。我怀疑这在大多数情况下都可以正常工作。从我的角度来看,不利的一面是它意味着TFont
作为模板参数引入:它不仅是一个类Word
(包含一个Font
实例),它需要成为一个泛型类(如WordT<TFont>
)......这也是任何包含WordT<TFont>
(eg Paragraph
) 的类现在也需要成为具有TFont
参数 (eg ParagraphT<TFont>
) 的泛型。最终,程序集中的几乎每个类都变成了泛型类。这确实保留了类型安全性并避免了向下转换的需要......但是它有点难看,并且扰乱了封装的错觉(“字体”是不透明的实现细节的错觉)。
另一个答案是在用户类中使用地图或字典。而不是Font
在可重用库中,而不是抽象接口,定义一个“句柄”类,如:
public struct FontHandle
{
public readonly int handleValue;
FontHandle(int handleValue)
{
this.handleValue = handleValue;
}
}
然后,不要从 向下转换,而是FontHandle
保留一个将值Dictionary<int, Font>
映射到实例的实例。FontHandle
Font