Проблема дублирования кода ортогональных переменных - PullRequest
1 голос
/ 17 сентября 2008

Я недавно начал рефакторинг некоторого устаревшего кода и наткнулся на две функции для рисования координатной сетки, проблема в том, что эти функции отличаются только ортогональными переменными, которые они обрабатывают, что-то вроде этого

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

Так что, если я решу добавить какие-нибудь интересные вещи, такие как сглаживание или просто поменять карандаш для рисования, или что-то еще, мне придется поместить в них одинаковый код, и это будет дублирование кода, и это плохо, мы все знаем, почему.

Мой вопрос: как бы вы переписали эти две функции в одну, чтобы избежать этой проблемы?

Ответы [ 6 ]

6 голосов
/ 17 сентября 2008

Почему вы просто не извлекаете тело цикла for в отдельную функцию? Тогда вы можете делать забавные вещи в извлеченной функции.

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
}
2 голосов
/ 17 сентября 2008

Рисование линии - это просто объединение двух точек и масштабирование с приращением (x0, y0) и (x1, y1) в определенном направлении, через X и / или через Y. Это сводится к тому, в случае шкалы, в каком направлении происходит шаг (возможно, оба направления для развлечения).

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;

Шаблон выполнит свою работу: во время компиляции компилятор удалит все нулевые операторы, т. Е. DeltaX или deltaY равны 0 в зависимости от того, какая функция вызывается, и половина кода уходит в каждом функторе.

Вы можете добавить сглаживание, карандашные вещи внутри этой функции uniq и получить код, сгенерированный компилятором должным образом.

Это вырезать и вставить на стероидах; -)

- ppi

0 голосов
/ 17 сентября 2008

Маленькие шаблоны ...:)

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);
}
0 голосов
/ 17 сентября 2008

Я думаю, я бы переехал:

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

в свою собственную функцию DrawLine (x0, y0, x0, y0), которую вы можете вызывать из каждой из существующих функций.

Тогда есть ли одно место для добавления дополнительных эффектов рисования?

0 голосов
/ 17 сентября 2008

Что ж, очевидным «решением» было бы создание одной функции и добавление одного дополнительного параметра (типа enum-типа). А затем выполните if () или switch () внутри и выполните соответствующие действия. Потому что, эй, функциональность функций отличается, поэтому вы должны выполнять эти разные действия где-то .

Однако это добавляет сложность во время выполнения (проверяйте вещи во время выполнения) в месте, которое можно было бы просто лучше проверить во время компиляции.

Я не понимаю, в чем проблема с добавлением дополнительных параметров в будущем в обе (или более функции). Это выглядит так:

  1. добавить больше параметров ко всем функциям
  2. скомпилируйте ваш код, он не будет компилироваться в нескольких местах, потому что он не передает новые параметры.
  3. исправляет все места, которые вызывают эти функции, передавая новые параметры.
  4. прибыль! :)

Если это C ++, конечно, вы можете сделать функцию шаблоном, и вместо добавления дополнительного параметра вы добавляете параметр шаблона, а затем специализируете реализации шаблонов для выполнения разных задач. Но, на мой взгляд, это просто запутывает вопрос. Код становится все труднее понять, и процесс расширения его с помощью большего количества параметров по-прежнему точно такой же:

  1. добавить дополнительные параметры
  2. компилировать код, он не будет компилироваться в нескольких местах
  3. исправить все места, которые вызывают эту функцию

Итак, вы ничего не выиграли, но сделали код сложнее для понимания. Не достойная цель, ИМО.

0 голосов
/ 17 сентября 2008

Вот мое собственное решение


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

Но мне это кажется слишком сложным для такой крошечной проблемы, поэтому я с нетерпением жду возможности увидеть ваши решения.

...