Question

J'ai une logique qui définit et utilise certains types définis par l'utilisateur, comme ceux-ci:

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 logique, après avoir effectué des calculs avec les types (par exemple, pour localiser chaque Word instance), utilise indirectement certaines System API, par exemple en appelant la méthode Canvass.draw.

Je voudrais rendre cette logique indépendante de l'espace de noms System.Drawing: principalement pour faciliter les tests unitaires (je pense que la sortie des tests unitaires serait plus facile à vérifier si la méthode draw attirait quelque chose autre qu'une véritable System.Drawing.Graphics instance).

Pour éliminer la dépendance de la logique à l'espace de noms IMyFont, j'ai décidé de déclarer certaines nouvelles interfaces comme des espaces réservés pour les types IMyGraphics, par exemple:

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

Si je le faisais, alors différents assemblys pourraient avoir différentes implémentations des interfaces Canvass et TFont, par exemple ...

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

... toutefois, la mise en œuvre nécessiterait un abaissement comme illustré ci-dessus.

Ma question est la suivante: existe-t-il un moyen de le faire sans avoir besoin d'un downcast dans la mise en œuvre? Par & "this &"; je veux dire & "; Les UDT tels que Font et WordT<TFont> qui ne dépendent pas de types Paragraph spécifiques concrets & Quot;?

Une alternative serait les UDT abstraits ...

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

... mais cela aussi nécessiterait un downcast dans la mise en œuvre de la sous-classe.

J'ai aussi pensé à utiliser l'idiome à double dépêche, mais cela dépend de la dénomination des différentes sous-classes dans les API.

Ou, à défaut d'interfaces ou de sous-classes, existe-t-il un moyen d'utiliser des délégués?

- Modifier: -

Il y a eu deux réponses possibles.

Une solution consiste à utiliser des génériques, précisément comme suggéré par la réponse de 'Sir Lantis' ci-dessous, et comme suggéré par le billet de blog auquel John Skeet a lié. Je soupçonne que cela fonctionnerait bien dans la plupart des scénarios. L’inconvénient de mon point de vue est que cela signifie d’introduire ParagraphT<TFont> en tant que paramètre de modèle: ce n’est pas seulement une classe comme FontHandle (qui contient une instance Dictionary<int, Font>) qui doit devenir une classe générique ( comme <=>) ... c'est aussi que toute classe qui contient un <=> (par exemple <=>) doit maintenant devenir générique avec un paramètre <=> (par exemple <=>). Finalement, presque toutes les classes de l’assemblage sont devenues des classes génériques. Cela permet de préserver la sécurité des types et d'éviter de devoir descendre ... mais , c'est un peu moche et perturbe l'illusion de l'encapsulation (l'illusion que "Font" est une détail d'implémentation opaque).

Une autre solution consiste à utiliser une carte ou un dictionnaire dans la classe d'utilisateurs. Au lieu de <=> dans la bibliothèque réutilisable, et au lieu d’une interface abstraite, définissez une classe 'handle' comme:

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

Ensuite, au lieu de rétrograder à partir de <=>, conservez une instance <=> qui mappe <=> les valeurs à <=> instances.

Était-ce utile?

La solution

Tout d'abord, je me demande si le scénario dans son ensemble n'est pas un peu artificiel; allez-vous vraiment avoir besoin de ce niveau d'abstraction? Peut-être souscrivez-vous à YAGNI ?

Pourquoi votre MyGraphics fonctionne-t-il uniquement avec un MyFont? Peut-il fonctionner avec un IFont? Ce serait une meilleure utilisation des interfaces et éviterait tout ce problème ...

Une option pourrait être un peu une refonte, de sorte que le Point décrive simplement les métadonnées de la police (taille, police-face, etc.), et que vous ayez des éléments concrets <=> comme, par exemple:

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

et cela devient le travail des graphiques de faire la traduction - donc utilisé quelque chose comme:

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
}

Bien sûr, <=> pourrait également avoir besoin de la traduction ;-p

Autres conseils

Vous dites effectivement & "Je connais mieux que le compilateur - je sais que c'est forcément une instance de MyFont. &"; À ce stade, MyGraphics et IFont sont à nouveau étroitement couplés, ce qui réduit un peu le point de l'interface.

Est-ce que <=> devrait fonctionner avec n'importe quel <=> ou seulement avec un <=>? Si vous pouvez le faire fonctionner avec n'importe quel <=> tout ira bien. Dans le cas contraire, vous devrez peut-être consulter des génériques complexes pour sécuriser tous les types de compilation. Vous pouvez trouver mon article sur les génériques dans les tampons de protocole est utile dans les situations similaires.

(Suggestion secondaire - votre code sera plus idiomatiquement similaire à celui de .NET si vous suivez les conventions de dénomination, qui inclut le cas Pascal pour les méthodes.)

Je ne suis plus au courant de C # pour le moment - cela fait longtemps maintenant. Mais si vous ne voulez pas utiliser tous vos contenus ici, vous pourriez être obligé d'utiliser des génériques.

Je peux simplement fournir du code Java, mais C # devrait pouvoir faire la même chose avec le mot clé where.

Faites de votre interface une interface générique. En Java, ce serait

IMyGraphics<T extends IMyFont> puis MyGraphics : IMyGraphics<MyFont>

Redéfinissez ensuite la drawString signature pour prendre T font en tant que deuxième paramètre au lieu de IMyFont. Cela devrait vous permettre d'écrire

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

directement dans votre MyGraphics classe.

En C #, cela public interface IMyGraphics<T> where T:IMyFont devrait être <=>, mais je n'en suis pas tout à fait sûr.

Vous n'aimez pas le casting de IFont à MyFont? Vous pouvez faire ceci:

interface IFont {
    object Font { get; }
}

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

Bien sûr, vous devez toujours convertir System.Object en System.Drawing.Font dans la méthode de dessin, mais vous venez d'éliminer la dépendance à une implémentation de classe particulière (<=>).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top