题
我有一些逻辑,定义并使用了一些用户定义的类型,这样的:
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
实例),间接地使用了一些Canvass.draw
的API,例如
我想做出独立的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);
}
}
...然而的实施将需要一个低垂如上所示。
我的问题是,是有办法做到这一点,而无需向下转换的实现?按“本”,我的意思是“定义诸如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的命名不同子类。
或者,如果不与接口或子类,有一些方法使用委托?
<强> - 编辑: - 强>
已经有两个可能的答案。
答案之一是使用泛型,正是由“LANTIS先生的回答以下建议,并通过博客文章的建议约翰飞碟双向链接到。我怀疑这会在大多数情况下正常工作。从我的观点来看,不利的方面是,它是指将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
实例。
解决方案
首先,不知整个场景是不小的人造;你真的要需要这个抽象层次?也许订阅 YAGNI ?
为什么你MyGraphics
只能用MyFont
工作?它可与IFont
工作?这将是一个更好的使用界面,并会避免这一整个问题...
一个选择可能是有点重新设计,使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
工作,你会没事的。否则,你可能需要考虑复杂的仿制药,使这一切编译时类型安全。您可能会发现的我的交上泛型在协议缓冲区作为类似的情况是有用的。
(方的建议 - 如果你遵循的命名约定,其中包括方法帕斯卡尔情况下,你的代码会更加地道的.NET等)
我目前不是很清楚的C#了 - 已经有一段时间了。但是,如果你不想投你所有的东西在那里,你可能会被迫使用泛型。
我可以只提供Java代码,但C#应该能够经由where
关键字来这样做。
请您接口的通用接口。在Java中,这将是
IMyGraphics<T extends IMyFont>
然后MyGraphics : IMyGraphics<MyFont>
然后重新定义drawString
签名采取T font
作为第二个参数,而不是IMyFont
。这将使你写
public void drawString(string text, MyFont font, Point point)
直接插入MyGraphics
类。
在C#该IMyGraphics<T extends IMyFont>
应当public interface IMyGraphics<T> where T:IMyFont
,但我不100%肯定这一点。
您不喜欢从IFont
投给MyFont
?你可以这样做:
interface IFont {
object Font { get; }
}
class MyFont : IFont {
object Font { get { return ...; } }
}
当然,你还需要从System.Object
投在绘图方法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);
}