さて,ここで,上記例に戻る.
なるべく汎用性ということを考えて図形クラスの実装について考えてみると,
- Draw関数はPolygonクラスに実装すべきなのか.
- 図形ごとに特化すべきなのか
について検討する必要がある.
Drawする方法についてはコンパイル時におそらく決まっているであろう.
そのため下記のようなテンプレートとする.
class Polygon
{
template<class drawer>
void Draw() ( drawer d ) { d(*this); }
};
このようにすれば,関数オブジェクトDrawerを与えれば自由に図形を描画することが可能であろう.
点のみを描画するのか,点を線でつなぐのか,面を塗りつぶすのか.
struct DrawPoint
{
void operator()(const Polygon& p);
};
struct DrawLine
{
void operator()(const Polygon& p);
};
struct DrawFill
{
void operator()(const Polygon& p);
};
さらに点を描画するというアルゴリズムは自作したpolygonクラス以外でも可能とするべきであろう.
点データをもつコンテナでありイテレータでアクセスできればよいのである.もちろん点データが共通の基底クラスをもち,その基底クラスが点へのアクセスのためのインターフェイスを定義しているならば,テンプレートでなくその基底クラスを受ける形式でも構わない.しかしながら,全ての点データを派生させるよりも,PointsType::has_iteratorなどのコンセプトチェックをするほうがコストが安く柔軟性があるだろう.
template<class PointsType>
struct DrawPoint
{
void operator()(const PointsType& p);
};
template<class PointsType>
struct DrawLine
{
void operator()(const PointsType& p);
};
template<class PointsType>
struct DrawFill
{
void operator()(const PointsType& p);
};
次に,必要なことはコンテナである.最初の例ではデータはstd::vector<std::complex>に限定していた.
点は三次元になるかもしれないし,他のデータを表現するクラスになるかもしれない.そのような場合は以下のように抽象化できるであろう.
N次元に対して対応したものとなる.
template<class Ty, size_t N>
class Polygon
{
public:
template<class drawer>
void Draw(drawer d) { d(*this); }
protected:
std::vector< std::array<Ty, N> > data;
};
最後に図形ごとの特化について考える.このpolygonクラスに円を表すデータを入れて描画させたいとする.
ではCircleクラスはPolygonを継承すべきだろうか.それともPolygonを持つべきだろうか.それとも点データのみを持つべきだろうか.
円とはR^2 = (x-x0)^2 + (y-y0)^2を満たす式である.これはひとつの実装としてpolygonを持つと考えるべきだろうか,Polygon自体であると考えるべきか.
極限まで抽象化するならば,円は上記式でありそれ以上でもそれ以下でもなかろう.
template <typename Ty, size_t N >
class circle
{
public:
void SetRadius(Ty arg);
void SetCenter(const std::array<Ty, N>& arg);
void SetResolution(size_t arg);
bool equal( const std::array<Ty, N>& p) const;
bool greater_than( const std::array<Ty, N>& p) const;
bool less_than( const std::array<Ty, N>& p) const;
std::vector< std::array<Ty, N> >::const_iterator begin();
std::vector< std::array<Ty, N> >::const_iterator end();
protected:
void Make();
std::vector< std::array<Ty, N> > data;
std::array<Ty, N> center;
Ty radius;
size_t resolution;
};
式という特性で定義してみたCircleクラスである.
式という特性で定義してみたCircleクラスである.
大事なことなので二度書いておいた.当然,Polygonクラスを継承し,点データの外部からの変更を許さない仕様としてもよい.
この場合はCircleクラスをPolygonクラス以外の機能,式としての機能を重視しているということである.
ある点が円の式を満たすか,内部にあるのか外部にあるのかについて実装している.他の幾何図形も実装するならば,Equationクラスでも作って継承してもよいだろう.
このCircleクラスはイテレータでResolutionで指定されたデータを返す.円を半径や中心などの定数パラメータ以外で修正することは,許されないとし,const_iteratorのみとした.
ここでcircleとpolygonを効率よくつなぐためにpolygonにイテレータを受けるコンストラクタを作る.
template<typename InputIterator>
polygon(const InputIterator first, const InputIterator last) : container(first, last) {}
これにより下記のようにpolygonを初期化できる.
double radius = 10.0;
std::array<double, 2> center = { 3.0, 4.0 };
size_t resolution = 24;
circle<double, 2> c;
c.SetRadius( radius );
c.SetCenter( center );
c.SetResolution( resolution );
polygon polyCircle( c.begin(), c.end() );
polyCircle.Draw( DrawLine() ); //not implemented
他の図形が必要な場合でもこのような実装であれば簡単に追加することができるであろう.
前述したとおりこの実装は一例であり,本来アプリケーションが要求する仕様に応じて設計すべきものである.