Domanda

Ho qualche logica, che definisce e usa alcuni tipi definiti dall'utente, come questi:

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

La logica, dopo aver effettuato calcoli con i tipi (ad es. per individuare ciascuna Word istanza), utilizza indirettamente alcune System API, ad esempio invocando il metodo Canvass.draw.

Vorrei rendere questa logica indipendente dallo spazio dei nomi System.Drawing: principalmente, al fine di aiutare con i test unitari (penso che l'output dei test unitari sarebbe più facile da verificare se il metodo draw stava attingendo a qualcosa diverso da una System.Drawing.Graphics istanza reale).

Per eliminare la dipendenza della logica dallo spazio dei nomi IMyFont, ho pensato di dichiarare alcune nuove interfacce che fungessero da segnaposto per i tipi IMyGraphics, ad esempio:

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

Se lo facessi, diversi assembly potrebbero avere implementazioni diverse dell'interfaccia Canvass e TFont, ad esempio ...

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

... tuttavia l'implementazione avrebbe bisogno di un downcast come illustrato sopra.

La mia domanda è: c'è un modo per farlo senza aver bisogno di un downcast nell'implementazione? Per " questo " ;, intendo " definendo UDT come Font e WordT<TFont> che non dipendono da specifici Paragraph tipi & Quot ;?

Un'alternativa sarebbero le UDT astratte ...

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

... ma anche questo richiederebbe un downcast nell'implementazione della sottoclasse.

Ho anche pensato di usare il doppio linguaggio di invio, ma dipende dal nominare le varie sottoclassi nelle API.

Oppure, se non con interfacce o sottoclassi, c'è un modo per usare i delegati?


- Modifica: -

Ci sono state due possibili risposte.

Una risposta è usare i generici, proprio come suggerito dalla risposta "Sir Lantis" di seguito, e come suggerito dal post sul blog a cui John Skeet ha collegato. Sospetto che funzionerebbe bene nella maggior parte degli scenari. Il lato negativo dal mio punto di vista è che significa introdurre ParagraphT<TFont> come parametro modello: non è solo una classe come FontHandle (che contiene un'istanza Dictionary<int, Font>) che deve diventare una classe generica ( come <=>) ... è anche che qualsiasi classe che contiene un <=> (ad es. <=>) ora deve anche diventare generica con un parametro <=> (ad es. <=>). Alla fine, quasi ogni classe nell'assembly è diventata una classe generica. Questo preserva la sicurezza dei tipi ed evita la necessità di downcast ... ma è un po 'brutto e disturba l'illusione dell'incapsulamento (l'illusione che' Font 'sia un dettaglio di implementazione opaco).

Un'altra risposta è utilizzare una mappa o un dizionario nella classe utente. Invece di <=> nella libreria riutilizzabile e invece di un'interfaccia astratta, definisci una classe "handle" come:

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

Quindi, invece di eseguire il downcast da <=>, mantieni un'istanza <=> che mappa <=> i valori su <=> istanze.

È stato utile?

Soluzione

Per prima cosa, mi chiedo se l'intero scenario non sia un po 'artificiale; hai davvero bisogno questo livello di astrazione? Forse iscriviti a YAGNI ?

Perché il tuo MyGraphics funziona solo con un MyFont? Può funzionare con un IFont? Sarebbe un uso migliore delle interfacce ed eviterebbe l'intero problema ...

Un'opzione potrebbe essere un po 'una riprogettazione, in modo che Point descriva semplicemente i metadati per il font (dimensione, font-face, ecc.), e hai cose sul concreto <=> come:

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

e diventa compito della grafica fare la traduzione - quindi ho usato qualcosa come:

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
}

Naturalmente, <=> potrebbe anche essere necessaria una traduzione ;-p

Altri suggerimenti

Stai effettivamente dicendo " lo so meglio del compilatore - so che è destinato a essere un'istanza di MyFont. " A quel punto hai MyGraphics e IFont di nuovo strettamente accoppiati, il che riduce un po 'il punto dell'interfaccia.

<=> dovrebbe funzionare con qualsiasi <=> o solo con un <=>? Se riesci a farlo funzionare con qualsiasi <=>, starai bene. Altrimenti, potresti dover esaminare generici complicati per rendere sicuro tutto il tipo di tempo di compilazione. Puoi trovare il mio post su generics in Protocol Buffers utile come situazione simile.

(Suggerimento laterale: il tuo codice sarà più idiomaticamente simile a .NET se segui le convenzioni di denominazione, che include il caso Pascal per i metodi.)

Al momento non sono più a conoscenza di C # - è passato del tempo ormai. Ma se non vuoi lanciare tutte le tue cose lì, potresti essere costretto a usare generici.

Posso solo fornire il codice Java, ma C # dovrebbe essere in grado di fare lo stesso tramite la parola chiave where.

Rendi la tua interfaccia un'interfaccia generica. In Java sarebbe

IMyGraphics<T extends IMyFont> e quindi MyGraphics : IMyGraphics<MyFont>

Quindi ridefinire la drawString Firma per prendere T font come secondo parametro anziché IMyFont. Ciò dovrebbe consentirti di scrivere

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

direttamente nella tua MyGraphics classe.


In C # che public interface IMyGraphics<T> where T:IMyFont dovrebbe essere <=>, ma non ne sono sicuro al 100%.

Non ti piace il cast da IFont a MyFont? Puoi farlo:

interface IFont {
    object Font { get; }
}

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

Sicuramente devi ancora trasmettere da System.Object a System.Drawing.Font nel metodo di disegno, ma hai appena eliminato la dipendenza da un'implementazione di classe particolare (<=>).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top