题
我最近开始重构一些遗留代码,遇到了两个用于绘制坐标网格的函数,问题是这些函数仅在它们处理的正交变量上有所不同,类似这样
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);
}
}
因此,如果我决定添加一些奇特的东西,例如抗锯齿或仅仅改变绘图铅笔或其他任何东西,我将不得不在它们两者中放入相同的代码,这是代码重复,我们都知道为什么,这很糟糕。
我的问题是如何将这两个函数重写为一个函数以避免这个问题?
解决方案
绘制一条线只是连接两个点,并在特定方向上通过 X 和/或通过 Y 绘制递增的 (x0,y0) 和 (x1,y1) 缩放。这归结为,在比例情况下,发生哪个方向的步进(也许两个方向都是有趣的)。
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 函数中添加抗锯齿、铅笔等内容,并让编译器正确生成代码。
这是在类固醇上剪切和粘贴的;-)
-- 生产者指数
其他提示
为什么不将 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);
}