Domanda

Recentemente ho iniziato a rifattorizzare alcuni codici legacy e mi sono imbattuto in due funzioni per disegnare una griglia di coordinate, il problema è che queste funzioni differiscono solo per le variabili ortogonali che trattano, qualcosa del genere

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

Quindi, se decido di aggiungere alcune cose fantasiose, come l'antialiasing o semplicemente cambiare la matita da disegno o qualsiasi altra cosa, dovrò inserire lo stesso codice in entrambi ed è una duplicazione del codice ed è un male, sappiamo tutti il ​​perché.

La mia domanda è: come riscriveresti queste due funzioni in una sola per evitare questo problema?

È stato utile?

Soluzione

Disegnare una linea significa semplicemente unire due punti e tracciare una scala incrementale (x0,y0) e (x1,y1) in una direzione particolare, attraverso X e/o attraverso Y.Ciò si riduce, nel caso della scala, a quale direzione si verifica (forse entrambe le direzioni per divertimento).

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;

Il modello farà il suo lavoro:in fase di compilazione il compilatore rimuoverà tutte le istruzioni null, ad es.deltaX o deltaY è 0 riguardo a quale funzione viene chiamata e metà del codice scompare in ciascun funtore.

Puoi aggiungere anti-alias, materiale a matita all'interno di questa funzione uniq e ottenere il codice generato correttamente dal compilatore.

Questo è taglia e incolla con steroidi ;-)

--ppi

Altri suggerimenti

Perché semplicemente non estrai il corpo del ciclo for in una funzione separata?Quindi puoi fare cose divertenti nella funzione estratta.

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
}

Ecco la mia soluzione


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

Ma mi sembra troppo complicato per un problema così piccolo, quindi non vedo l'ora di vedere ancora le tue soluzioni.

Bene, una "soluzione" ovvia sarebbe quella di creare una singola funzione e aggiungere un parametro extra (di tipo enum).E poi esegui un if() o uno switch() all'interno ed esegui le azioni appropriate.Perché ehi, il funzionalità delle funzioni è diverso, quindi devi eseguire azioni diverse in qualche luogo.

Tuttavia, ciò aggiunge complessità di runtime (controlla le cose in fase di esecuzione) in un luogo che potrebbe essere controllato meglio in fase di compilazione.

Non capisco quale sia il problema nell'aggiungere parametri extra in futuro in entrambe (o più funzioni).Funziona così:

  1. aggiungere più parametri a tutte le funzioni
  2. compila il tuo codice, non verrà compilato in un sacco di posti perché non passa nuovi parametri.
  3. correggi tutti i posti che chiamano quelle funzioni passando nuovi parametri.
  4. profitto!:)

Se si tratta di C++, ovviamente potresti rendere la funzione un modello e, invece di aggiungere un parametro aggiuntivo, aggiungere un parametro di modello e quindi specializzare le implementazioni del modello per fare cose diverse.Ma questo, secondo me, non fa altro che offuscare il punto.Il codice diventa più difficile da comprendere e il processo per estenderlo con più parametri è fermo esattamente lo stesso:

  1. aggiungere parametri aggiuntivi
  2. compilare il codice, non verrà compilato in un sacco di posti
  3. correggi tutti i posti che chiamano quella funzione

Quindi non hai vinto nulla, ma hai reso il codice più difficile da capire.Non è un obiettivo degno, IMO.

Penso che mi sposterei:

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

nella propria funzione DrawLine(x0,y0,x0,y0), che puoi chiamare da ciascuna delle funzioni esistenti.

Allora c'è un posto dove aggiungere effetti di disegno extra?

Un po' di modelli...:)

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);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top