Frage

Ich habe eine gewisse Logik, die definiert und verwendet einige benutzerdefinierte Typen, wie diese:

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

Die Logik, nach Berechnungen mit den Typen zu tun (z.B. jede Word Instanz zu suchen), indirekt einige System APIs verwendet, beispielsweise durch die Canvass.draw Verfahren aufgerufen wird.

Ich mag diese Logik machen, unabhängig von der System.Drawing Namespace: meist, um mit Unit-Tests zu helfen (ich glaube, Ausgabe Unit-Tests einfacher wäre, wenn die draw Methode zeichnet andere etwas zu überprüfen als ein echten System.Drawing.Graphics Beispiel).

Um die Logik der Abhängigkeit von dem System.Drawing Namensraum zu beseitigen, ich dachte, dass ich ein paar neue Schnittstellen erklären würde als Platzhalter für die System.Drawing Typen zu handeln, zum Beispiel:

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

Wenn ich das tue, dann verschiedene Baugruppen unterschiedliche Implementierungen des IMyFont und IMyGraphics Schnittstelle haben könnten, zum Beispiel ...

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

... aber die Umsetzung würde ein niedergeschlagene müssen wie oben dargestellt.

Meine Frage ist, gibt es eine Möglichkeit, dies zu tun, ohne einen niedergeschlagenen bei der Umsetzung zu benötigen? Mit „this“, meine ich „definieren UDTs wie Word und Canvass, die auf spezifische hängen nicht Beton System Typen "?

Eine Alternative wäre, abstrakt UDTs ...

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

... aber das würde auch braucht einen niedergeschlagene in der Umsetzung der Unterklasse.

Ich dachte auch an die Doppel-Ausgabe Idiom verwenden, aber es hängt von den verschiedenen Unterklassen in den APIs zu benennen.

Oder, wenn nicht mit Schnittstellen oder Unterklassen, ist es eine Möglichkeit, mit Delegierten?


- Edit: -

Es gab zwei mögliche Antworten.

Eine Antwort ist, Generika zu verwenden, genau wie unten durch ‚Sir Lantis‘ Antwort vorgeschlagen, und wie durch die Blog-Post, an den John Skeet verknüpft vorgeschlagen. Ich vermute, dass dies in den meisten Szenarien in Ordnung funktionieren würde. Die Abwärtsseite aus meiner Sicht ist, dass es bedeutet, TFont als Template-Parameter Einführung: Es ist nicht nur eine Klasse wie Word ist (die eine Font Instanz enthält), die eine generische Klasse zu werden braucht (wie WordT<TFont>) ... es ist auch, dass jede Klasse, die eine WordT<TFont> enthält (zB Paragraph) jetzt muss auch mit einem TFont Parameter zur Gattungsbezeichnung geworden (zB ParagraphT<TFont>). Schließlich fast jede Klasse in der Versammlung hat eine generische Klasse worden. Das funktioniert erhalten Typsicherheit und vermeiden die Notwendigkeit, niedergeschlagenen ... und es ist irgendwie hässlich, und stört die Illusion der Kapselung (die Illusion, dass ‚Font‘ ein undurchsichtige Implementierung Detail).

Eine andere Antwort ist, eine Karte oder ein Wörterbuch in der Benutzerklasse zu verwenden. Statt Font in der Mehrweg-Bibliothek, und statt einer abstrakten Schnittstelle, definiert eine ‚Griff‘ Klasse wie:

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

Dann wird anstelle von einziehe von FontHandle, halten eine Dictionary<int, Font> Instanz, die FontHandle Werte abbildet Instanzen Font.

War es hilfreich?

Lösung

Erstens, ich frage mich, ob das gesamte Szenario nicht etwas künstlich ist; sind Sie wirklich auf Notwendigkeit dieser Ebene der Abstraktion? Vielleicht abonnieren Sie YAGNI ?

Warum hat Ihr MyGraphics nur mit einem MyFont arbeiten? Kann es mit einem IFont arbeiten? Das wäre eine bessere Nutzung der Schnittstellen sein, und würde dieses ganze Problem vermeiden ...

Eine Option könnte ein bisschen ein Re-Design, so dass die IFont nur die Metadaten für die Schrift beschreibt (Größe, font-face, usw.), und Sie haben die Dinge auf dem Beton MyGraphics wie:

[public|internal] MyFont GetFont(IFont font) {...} // or just Font

, und es wird die Aufgabe der Grafiken, die Übersetzung zu tun - so verwendet dann so etwas wie:

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
}

Natürlich Point müssen möglicherweise Übersetzung zu ;-p

Andere Tipps

Sie sagen, effektiv „Ich weiß besser als die Compiler -. Ich weiß, dass es gebunden ist eine Instanz von MyFont zu sein“ An diesem Punkt haben Sie wieder MyFont und MyGraphics ist eng gekoppelt bekommen, das den Punkt der Schnittstelle ein wenig reduziert.

Sollte die Arbeit mit jedem MyGraphics IFont oder nur ein MyFont? Wenn Sie es mit jedem IFont funktioniert alles wird gut. Andernfalls müssen Sie bei komplizierten Generika suchen es alle Kompilierung-Typ sicher zu machen. Sie können feststellen, mein Beitrag auf Generika in Protocol Buffers nützlich als einer ähnlichen Situation.

(Side-Vorschlag -. Ihr Code wird mehr idiomatisch .NET-like, wenn Sie die Namenskonventionen, die für Methoden Pascal Fall enthält)

Ich bin zur Zeit nicht ganz bewusst, C # mehr - hat einige Zeit jetzt gewesen. Aber wenn Sie nicht wollen, da alle deine Sachen werfen, werden Sie möglicherweise gezwungen Generika zu verwenden.

Ich kann nur bieten Java-Code, aber C # sollte das gleiche über das where Stichwort tun können.

Machen Sie Ihre Schnittstelle eine generische Schnittstelle. In Java, das wäre

IMyGraphics<T extends IMyFont> und dann MyGraphics : IMyGraphics<MyFont>

neu definieren dann die drawString Signature T font als zweiten Parameter statt IMyFont zu nehmen. Dies sollte Ihnen ermöglichen, schreiben

public void drawString(string text, MyFont font, Point point)

direkt in Ihre MyGraphics Klasse.


In C #, dass IMyGraphics<T extends IMyFont> public interface IMyGraphics<T> where T:IMyFont werden soll, aber ich bin nicht 100% sicher.

Sie weiß nicht, wie die Besetzung von IFont MyFont? Sie können dies tun:

interface IFont {
    object Font { get; }
}

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

Sicher müssen Sie noch von System.Object werfen in der Zeichnung Methode System.Drawing.Font, aber Sie haben nur beseitigt die Abhängigkeit von bestimmten Klassenimplementierung (MyFont).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top