2

最近、いくつかのレガシー コードのリファクタリングを開始し、座標グリッドを描画するための 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);
    }
}

したがって、アンチエイリアシングや単に描画鉛筆を変更するなどの派手なものを追加することにした場合、それらの両方に同じコードを配置する必要があり、それはコードの重複であり、その理由は誰もが知っています。

私の質問は、この問題を回避するために、これら 2 つの関数を 1 つの関数にどのように書き直しますか?

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

線を描くということは、単純に 2 点を結び、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;

テンプレートはその仕事をします: コンパイル時に、コンパイラはすべての null ステートメントを削除します。つまり、どの関数が呼び出されたかに関して deltaX または deltaY は 0 であり、コードの半分は各ファンクターで消えます。

アンチエイリアスを追加し、この uniq 関数内にペンシルを追加して、コンパイラによって適切に生成されたコードを取得できます。

これはステロイドのカットアンドペーストです;-)

-- ppi

于 2008-09-17T10:28:21.280 に答える
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 に答える
0

明らかな「解決策」は、単一の関数を作成し、(列挙型のような) パラメーターを 1 つ追加することです。内部で 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) に変換します。これは、既存の各関数から呼び出すことができます。

次に、追加の描画効果を追加する場所が 1 つありますか?

于 2008-09-17T07:20:23.340 に答える
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 に答える