Проблема дублирования кода ортогональных переменных
-
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() и выполните соответствующие действия.Потому что, эй, функциональность функции разные, поэтому вам придется выполнять разные действия где-то.
Однако это добавляет сложности во время выполнения (проверяйте вещи во время выполнения) в месте, которое лучше проверять во время компиляции.
Я не понимаю, в чем проблема с добавлением дополнительных параметров в будущем в обе (или несколько функций).Это выглядит следующим образом:
- добавить больше параметров ко всем функциям
- скомпилируйте свой код, он не будет компилироваться во многих местах, потому что не передает новые параметры.
- исправьте все места, которые вызывают эти функции, передав новые параметры.
- выгода!:)
Если это C++, то, конечно, вы можете сделать функцию шаблоном и вместо добавления дополнительного параметра добавить параметр шаблона, а затем специализировать реализации шаблона для выполнения разных задач.Но это, на мой взгляд, просто запутывает суть.Код становится сложнее понять, а процесс расширения его большим количеством параметров все еще не решен. точно одинаковый:
- добавить дополнительные параметры
- скомпилируйте код, он не скомпилируется во многих местах
- исправить все места, которые вызывают эту функцию
Таким образом, вы ничего не выиграли, но усложнили понимание кода.Не достойная цель, ИМХО.
Думаю, я бы переехал:
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);
}