正交变量代码重复问题
我最近开始重构一些旧代码,并遇到了两个用于绘制坐标网格的函数,问题是这些函数仅在它们处理的正交变量上有所不同,例如
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); } }
因此,如果我决定添加一些奇特的东西,例如抗锯齿或者仅更换绘图铅笔,或者我必须在两者中放入相同的代码,并且代码重复,那么很糟糕,我们都知道原因。
我的问题是,如何将这两个函数重写为一个函数以避免发生此问题?
解决方案
回答
这是我自己的解决方案
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()); }
但是对于这样一个小问题,我觉得这太复杂了,因此我很期待看到解决方案。
回答
为什么只不将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 }
回答
好吧,一个显而易见的"解决方案"是制作一个函数并添加一个额外的参数(类似枚举的类型)。然后在内部执行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); }
回答
画一条线就是简单地将两个点连接起来,然后在特定方向上通过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函数中添加抗锯齿,铅笔等内容,并获得由编译器正确生成的代码。
这是剪切并粘贴在类固醇上的;-)
-ppi