2008-09-17 4 views
1

私は最近、いくつかのレガシーコードをリファクタリング開始し、座標グリッドを描画するための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つに書き直すとどうでしょうか?

答えて

2

単にを通じて、2点を結ぶ、そして特定の方向に(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; 

テンプレートは、そのジョブを実行します:コンパイル時にコンパイラがすべてのNULL文すなわちDELTAXを削除しますかdeltaY移動が呼ばれると、コードの半分が各数子に離れて行くされている機能に関する0です。

このuniq関数の中にアンチエイリアス、鉛筆を追加して、コンパイラが生成したコードを適切に生成できます。

これは、切断およびステロイド;-)

にペーストである - それはY0をチェックしないDrawScaleXのオリジナルコードにおいてPPI

+0

+1非常にエレガント –

+0

は<= Y1です。しかし、テンプレートバージョンでは、y0 <= y1であることが検証されます。したがって、アプリケーションが期待どおりに機能しないという問題が発生する可能性があります。 Matejによるソリューションのチェックアウト – Fred

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

あるしかし、私は私には、このような小さな問題の複雑すぎると思われるので、私はまだあなたのソリューションを見て楽しみにしています。

6

なぜサイクルの本体を別の機能に抽出しないのですか?次に、抽出された関数で面白いことをすることができます。

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 
} 
0

明白な「解決策」は、単一の関数を作成し、(enumのような型の)追加のパラメータを1つ追加することです。そしてif()またはswitch()を内部で実行し、適切なアクションを実行します。ちょっと、の機能の機能が異なるので、これらの異なるアクションを実行する必要がありますどこか

しかし、これは実行時の複雑さ(実行時にチェックする)を、コンパイル時にもっとよくチェックできる場所に追加します。

今後両方の(またはそれ以上の)機能でパラメータを追加する際に何が問題になるのかよく分かりません。それはこのように書きます:

  1. それが新しいパラメータを渡すことはありませんので、あなたのコードをコンパイル
  2. すべての機能をより多くのパラメータを追加し、それは場所の束にコンパイルされません。
  3. は、新しいパラメータを渡すことによってそれらの関数を呼び出すすべての場所を修正します。
  4. お得! :)

もちろん、関数をテンプレートにして、余分なパラメータを追加すると、テンプレートパラメータを追加して、テンプレート実装を特殊化してさまざまなことを行うことができます。しかし、これは私の意見では、ポイントを難読化しているだけです。コードを理解することが難しくなり、より多くのパラメータでそれを拡張するプロセスがまだ正確に同じ次のとおりです。

  1. は、余分なパラメータ
  2. コンパイルコードを追加し、それは場所
  3. の束にコンパイルされません
  4. は、その関数を呼び出すすべての場所を修正します。

したがって、何も勝ちませんでしたが、コードを理解しにくくしました。価値ある目標ではない、IMO。

0

私が動くと思う:

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

を使用すると、既存の各機能から呼び出すことができる、独自の関数DrawLine(X0、Y0、X0、Y0)へ。

次に、描画エフェクトを追加する場所は1か所ありますか?線を引く

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