Pergunta

Comecei a refatorar alguns códigos legados recentemente e me deparei com duas funções para desenhar uma grade de coordenadas, o problema é que essas funções diferem apenas nas variáveis ​​ortogonais que tratam, algo assim

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int x = x0; x < x1; x += step)
    {
         MoveToEx(dc, x, y0, NULL);
         LineTo(dc, x, y1);
    }
}
void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int y = y0; y < y1; y += step)
    {
         MoveToEx(dc, x0, y, NULL);
         LineTo(dc, x1, y);
    }
}

Então, se eu decidir adicionar algumas coisas sofisticadas, como antialiasing ou simplesmente mudar o lápis de desenho ou qualquer outra coisa, terei que colocar o mesmo código em ambos e é duplicação de código e é ruim, todos nós sabemos o porquê.

Minha pergunta é como você reescreveria essas duas funções em uma única para evitar esse problema?

Foi útil?

Solução

Desenhar uma linha é simplesmente unir dois pontos e desenhar uma escala incrementando (x0,y0) e(x1,y1) em uma direção específica, através de X e/ou através de Y.Isso se resume, no caso da escala, em quais direções o passo ocorre (talvez em ambas as direções, por diversão).

template< int XIncrement, YIncrement >
struct DrawScale
{
  void operator()(HDC dc, int step, int x0, int x1, int y0, int y1)
  {
    const int deltaX = XIncrement*step;
    const int deltaY = YIncrement*step;
    const int ymax = y1;
    const int xmax = x1;
    while( x0 < xmax && y0 < ymax )
    {
        MoveToEx(dc, x0, y0, NULL);
        LineTo(dc, x1, y1);
        x0 += deltaX;
        x1 += deltaX;
        y0 += deltaY;
        y1 += deltaY;
    }
  }
};
typedef DrawScale< 1, 0 > DrawScaleX;
typedef DrawScale< 0, 1 > DrawScaleY;

O modelo fará seu trabalho:em tempo de compilação, o compilador removerá todas as instruções nulas, ou seja,deltaX ou deltaY é 0 em relação a qual função é chamada e metade do código desaparece em cada functor.

Você pode adicionar anti-alias, lápis dentro desta função uniq e obter o código gerado corretamente pelo compilador.

Isso é recortado e colado com esteróides ;-)

--ppi

Outras dicas

Por que você simplesmente não extrai o corpo do ciclo for em uma função separada?Então você pode fazer coisas engraçadas na função extraída.

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int x = x0; x < x1; x += step)
    {
        DrawScale(dc, x, y0, x, y1);
    }
}

void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int y = y0; y < y1; y += step)
    {
        DrawScale(dc, x0, y, x1, y);
    }
}

private void DrawScale(HDC dc, int x0, int y0, int x1, int y1)
{
    //Add funny stuff here

    MoveToEx(dc, x0, y0, NULL);
    LineTo(dc, x1, y1);

    //Add funny stuff here
}

Aqui está minha própria solução


class CoordGenerator
{
public:
    CoordGenerator(int _from, int _to, int _step)
        :from(_from), to(_to), step(_step), pos(_from){}
    virtual POINT GetPoint00() const = 0;
    virtual POINT GetPoint01() const = 0;
    bool Next()
        {
            if(pos > step) return false;
            pos += step;
        }
protected:
    int from;
    int to;
    int step;
    int pos;
};

class GenX: public CoordGenerator
{
public:
    GenX(int x0, int x1, int step, int _y0, int _y1)
        :CoordGenerator(x0, x1, step),y0(_y0), y1(_y1){}
    virtual POINT GetPoint00() const
        {
            const POINT p = {pos, y0};
            return p;
        }
    virtual POINT GetPoint01() const
        {
            const POINT p = {pos, y1};
            return p;
        }
private:
    int y0;
    int y1;
};

class GenY: public CoordGenerator
{
public:
    GenY(int y0, int y1, int step, int _x0, int _x1)
        :CoordGenerator(y0, y1, step),x0(_x0), x1(_x1){}
    virtual POINT GetPoint00() const
        {
            const POINT p = {x0, pos};
            return p;
        }
    virtual POINT GetPoint01() const
        {
            const POINT p = {x1, pos};
            return p;
        }
private:
    int x1;
    int x0;
};

void DrawScale(HDC dc, CoordGenerator* g)
{
    do
    {
        POINT p = g->GetPoint00();
        MoveToEx(dc, p.x, p.y, 0);
        p = g->GetPoint01();
        LineTo(dc, p.x, p.y);
    }while(g->Next());
}

Mas me parece muito complicado para um problema tão pequeno, então estou ansioso para ver suas soluções.

Bem, uma "solução" óbvia seria criar uma única função e adicionar um parâmetro extra (do tipo enum).E então faça um if() ou switch() dentro e execute as ações apropriadas.Porque ei, o funcionalidade das funções é diferente, então você tem que fazer essas ações diferentes em algum lugar.

No entanto, isso adiciona complexidade ao tempo de execução (verifique as coisas em tempo de execução) em um local que poderia ser melhor verificado em tempo de compilação.

Não entendo qual é o problema em adicionar parâmetros extras no futuro em ambas (ou em mais funções).É assim:

  1. adicione mais parâmetros a todas as funções
  2. compilar seu código, ele não será compilado em vários lugares porque não passa novos parâmetros.
  3. corrija todos os locais que chamam essas funções passando novos parâmetros.
  4. lucro!:)

Se for C++, é claro que você poderia fazer com que a função fosse um modelo e, em vez disso, adicionar um parâmetro extra, adicionar um parâmetro de modelo e, em seguida, especializar implementações de modelo para fazer coisas diferentes.Mas isso está apenas ofuscando o ponto, na minha opinião.O código se torna mais difícil de entender e o processo de estendê-lo com mais parâmetros ainda é exatamente o mesmo:

  1. adicione parâmetros extras
  2. compilar o código, ele não será compilado em vários lugares
  3. corrija todos os lugares que chamam essa função

Então você não ganhou nada, mas tornou o código mais difícil de entender.Não é um objetivo digno, IMO.

Acho que mudaria:

     MoveToEx(dc, x0, y, NULL);
     LineTo(dc, x1, y);

em sua própria função DrawLine(x0,y0,x0,y0), que você pode chamar de cada uma das funções existentes.

Então há um lugar para adicionar efeitos de desenho extras?

Alguns modelos...:)

void DrawLine(HDC dc, int x0, int y0, int x0, int x1)
{
    // anti-aliasing stuff
    MoveToEx(dc, x0, y0, NULL);
    LineTo(dc, x1, y1);
}

struct DrawBinderX
{
    DrawBinderX(int y0, int y1) : y0_(y0), y1_(y1) {}

    void operator()(HDC dc, int i)
    {
        DrawLine(dc, i, y0_, i, y1_);
    }

private:
    int y0_;
    int y1_;

};

struct DrawBinderY
{
    DrawBinderX(int x0, int x1) : x0_(x0), x1_(x1) {}

    void operator()(HDC dc, int i)
    {
        DrawLine(dc, x0_, i, x1_, i);
    }

private:
    int x0_;
    int x1_;

};

template< class Drawer >
void DrawScale(Drawer drawer, HDC dc, int from, int to, int step)
{
    for (int i = from; i < to; i += step)
    {
         drawer(dc, i);
    }
}

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    DrawBindexX drawer(y0, y1);
    DrawScale(drawer, dc, x0, x1, step);
}

void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    DrawBindexY drawer( x0, x1 );
    DrawScale(drawer, dc, y0, y1, step);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top