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实例)之后,间接使用一些SystemAPI,例如通过调用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);
  }
}

...但是,如上图所示,实现将需要向下转换。

我的问题是,有没有一种方法可以做到这一点而无需在实施过程中感到沮丧?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>映射到实例的实例。FontHandleFont

4

4 回答 4

2

首先,我想知道整个场景是不是有点人为;你真的需要这种抽象级别吗?也许订阅YAGNI

为什么你MyGraphics只使用 a 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

于 2009-02-02T13:12:59.713 回答
2

您实际上是在说“我比编译器更了解 - 我知道它一定是MyFont. 那时你已经得到MyFont并且MyGraphics再次紧密耦合,这会稍微减少接口的点。

应该MyGraphics使用任何IFont,或只使用MyFont? 如果你可以让它与任何IFont你一起工作,你会没事的。否则,您可能需要查看复杂的泛型以使其所有编译时类型安全。您可能会发现我在 Protocol Buffers 中关于泛型的帖子在类似情况下很有用。

(附带建议 - 如果您遵循命名约定,您的代码将更像 .NET,其中包括方法的 Pascal 大小写。)

于 2009-02-02T13:14:02.083 回答
1

我目前不太了解 C# - 现在已经有一段时间了。但是如果你不想把你所有的东西都放在那里,你可能会被迫使用泛型。

我可以只提供 Java 代码,但 C# 应该能够通过where关键字来做同样的事情。

使您的接口成为通用接口。在Java中,那将是

IMyGraphics<T extends IMyFont> 进而MyGraphics : IMyGraphics<MyFont>

然后重新定义drawStringSignatureT 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% 确定的。

于 2009-02-02T13:38:48.773 回答
0

你不喜欢演员表 from IFonttoMyFont吗?你可以这样做:

interface IFont {
    object Font { get; }
}

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

当然,您仍然需要在绘图方法中转换 from System.Objectto 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 回答