Проблема дублирования кода ортогональных переменных

StackOverflow https://stackoverflow.com/questions/80691

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

Рисование линии — это просто соединение двух точек и рисование масштабного приращения (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

Другие советы

Почему вы просто не выделяете тело цикла 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
}

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Немного шаблонов...:)

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);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top