Pregunta

Tengo algo de lógica, que define y utiliza algunos tipos definidos por el usuario, como estos:

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 lógica, después de hacer los cálculos con el tipo (por ejemplo,para localizar cada uno de los Word ejemplo), de forma indirecta utiliza algunos System Api, por ejemplo mediante la invocación de la Canvass.draw método.

Me gustaría hacer esta lógica independiente de la System.Drawing espacio de nombres:en su mayoría, con el fin de ayudar con la unidad de pruebas (creo que las pruebas de unidad salida sería más fácil de comprobar si el draw método de dibujo para algo que no sea real System.Drawing.Graphics instancia).

Para eliminar la lógica de la dependencia en la System.Drawing espacio de nombres, yo pensaba que iba a declarar algunos de los nuevos interfaces para actuar como marcadores de posición para el System.Drawing tipos, por ejemplo:

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 yo hice esto, entonces los diferentes asambleas podrían tener diferentes implementaciones de la IMyFont y IMyGraphics la interfaz, por ejemplo ...

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

...sin embargo, la aplicación tendría un abatido como se ilustra arriba.

Mi pregunta es, es allí una manera de hacer esto sin necesidad de un abatido en la aplicación? Por "esto", me refiero a "la definición de tipos definidos por el usuario como Word y Canvass que no dependen de hormigón específicos System tipos"?

Una alternativa sería abstracto tipos definidos por el usuario ...

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

...pero esto también tendría un abatido en la aplicación de la subclase.

También he pensado en usar el doble de envío modismo, pero depende de la denominación de las diversas subclases en la Api.

O, si no con interfaces o subclases, hay alguna manera de usar los delegados?


--Edit:--

Ha habido dos respuestas posibles.

Una respuesta es el uso de medicamentos genéricos, precisamente, como sugiere el Señor 'Utiliza' respuesta a continuación, y como se sugiere por el blog para que Juan Skeet vinculados.Sospecho que esto iba a funcionar bien en la mayoría de los escenarios.El lado negativo desde mi punto de vista es que significa la introducción de TFont como un parámetro de plantilla:no es sólo una clase de Word (que contiene un Font instancia), que debe convertirse en una clase genérica (como WordT<TFont>) ...también que cualquier clase que contiene un WordT<TFont> (por ejemplo, Paragraph ahora también debe ser genérico con un TFont parámetro (por ejemplo, ParagraphT<TFont>).Finalmente, casi todos los de la clase en la asamblea se ha convertido en una clase genérica.Este ¿ preservar la seguridad de tipos y evitar la necesidad de abatido ... pero es un poco feo, y perturba la ilusión de encapsulación (la ilusión de que 'Fuente' es un opaco detalle de implementación).

Otra respuesta es el uso de un mapa o un diccionario en la clase de usuario.En lugar de Font en la biblioteca reutilizable, y en lugar de una interfaz abstracta, definir una "manija" de la clase como:

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

Entonces, en lugar de downcasting de FontHandle, mantener un Dictionary<int, Font> instancia que asigna FontHandle los valores de Font las instancias.

¿Fue útil?

Solución

Primero, me pregunto si todo el escenario no es un poco artificial; ¿realmente vas a necesitar este nivel de abstracción? Quizás suscribirse a YAGNI ?

¿Por qué su MyGraphics solo funciona con un MyFont? ¿Puede funcionar con un IFont? Sería un mejor uso de las interfaces y evitaría todo este problema ...

Una opción podría ser un poco rediseñado, de modo que Point solo describa los metadatos para la fuente (tamaño, fuente, etc.), y tenga cosas en concreto <=> como:

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

y se convierte en el trabajo de los gráficos hacer la traducción, por lo que usó algo como:

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
}

Por supuesto, <=> también podría necesitar traducción ;-p

Otros consejos

Que efectivamente está diciendo: "yo sé mejor que el compilador - sé que está destinado a ser una instancia de MyFont." En ese punto tienes MyFont y MyGraphics está estrechamente unida de nuevo, lo que reduce el punto de la interfaz un poco.

Debe MyGraphics trabajar con cualquier IFont, o sólo una MyFont?Si usted puede hacer que funcione con cualquier IFont vas a estar bien.De lo contrario, puede que tenga que buscar en el complicado genéricos a hacer todo tipo en tiempo de compilación seguro.Usted puede encontrar a mi post sobre los medicamentos genéricos en los Búferes de Protocolo útil como una situación similar.

(Lado de la sugestión, el código será más idiomáticamente .NET-como si usted sigue las convenciones de nomenclatura, que incluye Pascal caso de métodos).

Actualmente no estoy al tanto de C #, ya ha pasado algún tiempo. Pero si no desea lanzar todas sus cosas allí, es posible que se vea obligado a usar genéricos.

Solo puedo proporcionar código Java, pero C # debería poder hacer lo mismo a través de la palabra clave where.

Convierta su interfaz en una interfaz genérica. En Java eso sería

IMyGraphics<T extends IMyFont> y luego MyGraphics : IMyGraphics<MyFont>

Luego redefina la Firma drawString para tomar T font como el segundo parámetro en lugar de IMyFont. Esto debería permitirle escribir

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

directamente en su MyGraphics clase.


En C # que public interface IMyGraphics<T> where T:IMyFont debería ser <=>, pero no estoy 100% seguro de eso.

¿No te gusta el reparto de IFont a MyFont? Puedes hacer esto:

interface IFont {
    object Font { get; }
}

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

Seguro que todavía necesita convertir de System.Object a System.Drawing.Font en el método de dibujo, pero acaba de eliminar la dependencia de una implementación de clase particular (<=>).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top