2

我最近开始重构一些遗留代码并遇到了两个用于绘制坐标网格的函数,问题是这些函数仅在它们处理的正交变量上有所不同,就像这样

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

因此,如果我决定添加一些花哨的东西,例如抗锯齿或仅仅更改绘图铅笔或其他任何东西,我将不得不在它们两者中放入相同的代码,这是代码重复,这很糟糕,我们都知道为什么。

我的问题是如何将这两个函数重写为一个函数以避免这个问题?

4

6 回答 6

6

为什么不将 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
}
于 2008-09-17T07:18:39.537 回答
2

画一条线只是简单地连接两个点,并在特定方向上通过 X 和/或 Y 绘制一个递增 (x0,y0) 和 (x1,y1) 的缩放比例。这归结为,在比例情况下,方向(S)步进发生(可能是两个方向的乐趣)。

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

于 2008-09-17T10:28:21.280 回答
0

这是我自己的解决方案


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

但在我看来,对于这么一个小问题来说太复杂了,所以我仍然期待看到你的解决方案。

于 2008-09-17T07:13:19.097 回答
0

好吧,一个明显的“解决方案”是创建一个函数并添加一个额外的参数(类似枚举的类型)。然后在里面执行 if() 或 switch(),并执行相应的操作。因为嘿,函数的功能是不同的,所以你必须在某个地方做那些不同的动作。

然而,这增加了运行时的复杂性(在运行时检查),而这个地方可以在编译时更好地检查。

我不明白将来在两个(或更多功能)中添加额外参数有什么问题。它是这样的:

  1. 为所有函数添加更多参数
  2. 编译你的代码,它不会在一堆地方编译,因为它不传递新参数。
  3. 通过传递新参数来修复所有调用这些函数的地方。
  4. 利润!:)

如果是 C++,当然可以将函数设为模板,而是添加一个额外的参数,添加一个模板参数,然后专门化模板实现来做不同的事情。但在我看来,这只是混淆了这一点。代码变得更难理解了,用更多的参数来扩展它的过程还是一模一样的:

  1. 添加额外参数
  2. 编译代码,它不会在一堆地方编译
  3. 修复所有调用该函数的地方

所以你什么也没赢,但让代码更难理解。不是一个有价值的目标,IMO。

于 2008-09-17T07:19:19.910 回答
0

我想我会搬家:

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

到他们自己的函数 DrawLine(x0,y0,x0,y0) 中,您可以从每个现有函数中调用该函数。

那么有一个地方可以添加额外的绘图效果吗?

于 2008-09-17T07:20:23.340 回答
0

一些模板... :)

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);
}
于 2008-09-17T07:47:59.077 回答