Pergunta

Eu tenho alguma lógica, que define e usa alguns tipos definidos pelo usuário, como estes:

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

A lógica, depois de fazer cálculos com os tipos (por exemplo, para localizar cada instância Word), indiretamente utiliza algumas APIs System, por exemplo, chamando o método Canvass.draw.

Eu gostaria de fazer este independente lógica do namespace System.Drawing: na maior parte, a fim de ajudar com os testes de unidade (acho saída testes de unidade seria mais fácil para verificar se o método draw estavam chegando para algo diferente de um verdadeiro instância System.Drawing.Graphics).

Para eliminar a dependência da lógica no namespace System.Drawing, eu pensei que eu iria declarar algumas novas interfaces para agir como espaços reservados para os tipos System.Drawing, por exemplo:

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 eu fizesse isso, então conjuntos diferentes pode ter diferentes implementações da interface IMyFont e IMyGraphics, por exemplo ...

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

... no entanto a implementação seria necessário um abatido como ilustrado acima.

A minha pergunta é, há uma maneira de fazer isso sem precisar de um abatido na implementação? por "isto", quero dizer "definir UDTs como Word e Canvass que não dependem específica tipos System concretas "?

Uma alternativa seria abstrato 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); 
}

... mas isso também iria precisar de um abatido na implementação da subclasse.

Eu também pensei em usar a linguagem dupla expedição, mas isso depende de nomear as várias subclasses nas APIs.

Ou, se não com interfaces ou subclasses, há alguma maneira usando delegados?


- Edit: -

Houve duas respostas possíveis.

Uma resposta é a utilização de genéricos, precisamente como sugerido pela resposta 'Sir Lantis' abaixo, e como sugere o post em que John Skeet ligados. Eu suspeito que isso iria funcionar bem na maioria dos cenários. O lado negativo do meu ponto de vista é que ele significa introduzir TFont como um parâmetro de modelo: não é só uma classe como Word (que contém uma instância Font) que precisa para se tornar uma classe genérica (como WordT<TFont>) ... é também que qualquer classe que contém uma WordT<TFont> (por exemplo Paragraph) agora também precisa ser genérica com um parâmetro TFont (por exemplo ParagraphT<TFont>). Eventualmente, quase todas as classes na montagem tornou-se uma classe genérica. Este não preservar tipo-segurança e evitar a necessidade de abatido ... e é meio feio, e perturba a ilusão de encapsulamento (a ilusão de que 'Font' é um detalhe de implementação opaco).

Outra resposta é usar um mapa ou dicionário na classe de usuário. Em vez de Font na biblioteca reutilizável, e em vez de uma interface abstrata, definir uma classe 'pega' como:

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

Então, em vez de downcasting de FontHandle, mantenha uma instância Dictionary<int, Font> que mapeia valores FontHandle a instâncias Font.

Foi útil?

Solução

Em primeiro lugar, gostaria de saber se todo o cenário não é um pouco artificial; você está realmente indo para necessidade este nível de abstração? Talvez subscrever YAGNI ?

Por que seu MyGraphics só funcionam com um MyFont? ele pode trabalhar com um IFont? Isso seria uma melhor utilização de interfaces, e evitaria toda esta questão ...

Uma opção poderia ser um pouco de um re-design, de modo que o IFont apenas descreve os metadados para o tipo de letra (tamanho, font-face, etc), e você tem coisas na MyGraphics concreto como:

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

e torna-se o trabalho dos gráficos para fazer a tradução - algo tão então utilizado 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
}

Claro, Point pode precisar de tradução também ;-p

Outras dicas

Você está dizendo efetivamente "Eu sei melhor do que o compilador - Eu sei que ele é obrigado a ser uma instância de MyFont." Nesse ponto você tem MyFont e MyGraphics sendo fortemente acoplado de novo, o que reduz o ponto da interface um pouco.

deve funcionar MyGraphics com qualquer IFont, ou apenas uma MyFont? Se você pode fazê-lo funcionar com qualquer IFont você vai ficar bem. Caso contrário, você pode precisar de olhar para os genéricos complicadas para torná-lo todo o tipo de tempo de compilação segura. Você pode encontrar meu post sobre os genéricos no Protocolo Buffers útil como uma situação semelhante.

(sugestão Side -. Seu código será mais idiomaticamente .NET-como se você seguir as convenções de nomenclatura, que inclui caso Pascal para métodos)

Atualmente, estou não completamente consciente de C # mais - tem sido algum tempo. Mas se você não quer para lançar todas as suas coisas lá, você pode ser forçado a medicamentos genéricos de uso.

Eu posso apenas fornecer o código Java, mas C # deve ser capaz de fazer o mesmo através da palavra-chave where.

Faça sua interface uma interface genérica. Em Java que seria

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

Em seguida, redefinir a assinatura drawString tomar T font como o segundo parâmetro em vez de IMyFont. Isso deve permitir que você escreva

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

diretamente em sua classe MyGraphics.


Em C # que IMyGraphics<T extends IMyFont> deve ser public interface IMyGraphics<T> where T:IMyFont, mas não estou 100% de certeza sobre isso.

Você não gosta do elenco de IFont para MyFont? Você pode fazer isso:

interface IFont {
    object Font { get; }
}

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

Claro que você ainda precisa de elenco de System.Object para System.Drawing.Font no método de desenho, mas você acabou eliminado a dependência especial implementação de classe (MyFont).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top