DATE : 2006/10/01 (Sun)
前回の記事の最後に、点オブジェクトを表すインタフェースとしてのクラスを作成しました。
class Point { public: virtual ~Point() { } virtual Point* clone() const = 0; virtual int getX() const = 0; virtual int getY() const = 0; virtual string toString() const = 0; };
このインタフェースを実装して、点そのものを表すクラスを考えます。
class PlainPoint : public Point { public : PlainPoint(); PlainPoint(const int& x, const int& y); Point* clone() const; int getX() const; int getY() const; string toString() const; private : int x; int y; }; PlainPoint::PlainPoint() : x(0), y(0) { } PlainPoint::PlainPoint(const int& x, const int& y) : x(x), y(y) { } Point* PlainPoint::clone() const { return new PlainPoint(this->x, this->y); } int PlainPoint::getX() const { return this->x; } int PlainPoint::getY() const { return this->y; } string PlainPoint::toString() const { ostringstream stream; stream << (this->x) << ", " << (this->y); return stream.str(); }
(;^ω^)コンストラクタの引数を参照にしたり、文字列を生成したりする部分以外はほとんど前の Point クラスと同じです。
例えば、次のように PlainPoint オブジェクトの文字列表現を出力してみます。
PlainPoint point(2, 3); cout << point.toString() << endl;
すると、次のような結果になります。
2, 3
ここで、次の部分に注目してください。
class PlainPoint : public Point { public : PlainPoint(); PlainPoint(const int& x, const int& y); Point* clone() const; int getX() const; int getY() const; string toString() const;
クラスの宣言に「: public 基底クラス」とすることで、基底クラスに指定したクラスを継承することができます。ちなみに、「public」とは、基底クラスの public メンバを派生クラスでもそのまま public にするという意味です。ここが「protected」だと基底クラスの public メンバは protected になりますし、「private」の場合は基底クラスのメンバ全てが private になります(public を省略した場合は、private になります)
続いて、PlainPoint クラスのコンストラクタ、コピーコンストラクタと続いて、この PlainPoint クラスで実装するメンバを並べています。virtual 修飾子は付いていませんが、基底クラスで(純粋)仮想関数であると宣言されているので、virtual 修飾子は必要ありません(ただし、省略可能であってもあえて virtual 修飾子を書く流派もあるようです)。
後は、普通にクラスを定義する場合と同じです。
続いて、修飾を施した点を表すクラスを考えてみます。
しかし、一口に「修飾」と言っても、修飾の種類は数多くあります。今回は文字列表現をいじるだけですので、カッコや名前を付けたりといった程度しかありませんが、実際に描画する際にはさらに多くの修飾が考えられます。例えば、周辺に及ぼす効果や色などが挙げられます。
ということで、まず修飾を施した点に共通した機能を抽出して抽象クラスを作ってみたいと思います。修飾を施した点というのは、修飾対象の点オブジェクトを保持していると考えられます。そこで、点オブジェクトを保持する DecoratedPoint クラスを考えます。
class DecoratedPoint : public Point { public: DecoratedPoint(Point *point); DecoratedPoint(const DecoratedPoint& source); virtual ~DecoratedPoint(); DecoratedPoint& operator=(const DecoratedPoint& source); Point* clone() const; int getX() const; int getY() const; string toString() const; private: Point* point; }; DecoratedPoint::DecoratedPoint(Point* point) : point(point) { } DecoratedPoint::DecoratedPoint(const DecoratedPoint& source) : point(source.point->clone()) { } DecoratedPoint& DecoratedPoint::operator=(const DecoratedPoint& source) { if (&source == this) { return *this; } delete this->point; this->point = source.point->clone(); return *this; } DecoratedPoint::~DecoratedPoint() { delete this->point; } Point* DecoratedPoint::clone() const { return this->point->clone(); } int DecoratedPoint::getX() const { return this->point->getX(); } int DecoratedPoint::getY() const { return this->point->getY(); } string DecoratedPoint::toString() const { return this->point->toString(); }
(;^ω^)やっていることは単純で、コンストラクタで受け取った Point オブジェクトに Point クラスとしての処理を横流しにしているだけです。
続いて、点に名前を付けるクラスを考えてみます。
(;^ω^)ただし、簡単にするため名前を変更する機能はありません。
class NamedPoint : public DecoratedPoint { public: NamedPoint(const string& name, Point* point); NamedPoint(const NamedPoint& source); NamedPoint& operator=(const NamedPoint& source); Point* clone() const; string toString() const; private: string name; }; NamedPoint::NamedPoint(const string& name, Point* point) : DecoratedPoint(point), name(name) { } NamedPoint::NamedPoint(const NamedPoint& source) : DecoratedPoint(source), name(source.name) { } NamedPoint& NamedPoint::operator=(const NamedPoint& source) { if (&source == this) { return *this; } DecoratedPoint::operator=(source); this->name = source.name; return *this; } Point* NamedPoint::clone() const { return new NamedPoint(this->name, DecoratedPoint::clone()); } string NamedPoint::toString() const { return this->name + DecoratedPoint::toString(); }
ここで、次の部分に注目してください。
NamedPoint::NamedPoint(const NamedPoint& source) : DecoratedPoint(source), name(source.name) { }
基底クラスのコンストラクタを初期化宣言で呼び出しています。このようにすると、基底クラスのコンストラクタを呼び出すことができます。
また、次の部分にも注目してください。
Point* NamedPoint::clone() const { return new NamedPoint(this->name, DecoratedPoint::clone()); }
「DecoratedPoint::clone()」とすることで、基底クラス DecoratedPoint の clone 関数を呼び出しています。このように、基底クラス名を指定すると、派生クラスからそのクラスの関数を呼び出すことができます。
(;^ω^)Java の super と少し似ていますね。
(;^ω^)ちなみに、上のように clone 関数を使わずに、保持している Point オブジェクトを返す関数を DecoratedPoint クラスに定義するという方法もあります。しかし、そのようにすると、修飾を行うクラスが修飾対象のオブジェクトに過剰に干渉できてしまうので、あえて保持している Point オブジェクトにアクセスできないようにしました。
最後に、カッコ付けを行うクラスを考えてみます。
class BracketedPoint : public DecoratedPoint { public: BracketedPoint(const string& left, const string& right, Point* point); BracketedPoint(const BracketedPoint& source); BracketedPoint& operator=(const BracketedPoint& source); Point* clone() const; string toString() const; private: string left; string right; }; BracketedPoint::BracketedPoint(const string& left, const string& right, Point* point) : DecoratedPoint(point), left(left), right(right) { } BracketedPoint::BracketedPoint(const BracketedPoint& source) : DecoratedPoint(source), left(source.left), right(source.right) { } BracketedPoint& BracketedPoint::operator=(const BracketedPoint& source) { if (&source == this) { return *this; } DecoratedPoint::operator=(source); this->left = source.left; this->right = source.right; return *this; } Point* BracketedPoint::clone() const { return new BracketedPoint( this->left, this->right, DecoratedPoint::clone()); } string BracketedPoint::toString() const { return this->left + DecoratedPoint::toString() + this->right; }
(;^ω^)このクラスで特に取り上げることはありません。
以上のクラスを使って、様々に修飾を施した、点の文字列表現を生成してみます。
次の例は、「name0, 0」となります。
NamedPoint point("name", new PlainPoint); cout << point.toString() << endl;
次の例は、「name(2, 3)」となります。
BracketedPoint point("(", ")", new PlainPoint(2, 3)); NamedPoint namedPoint("name", point.clone()); cout << namedPoint.toString() << endl;
次の例は、「[name(5, 6)]」となります。
BracketedPoint point("(", ")", new PlainPoint(5, 6)); NamedPoint namedPoint("name", point.clone()); BracketedPoint bracketedPoint("[", "]", namedPoint.clone()); cout << bracketedPoint.toString() << endl;
ちなみに、これらは全て Point クラスとして扱えるので、Line クラスに渡して使うことも可能です。例えば、以下の例では「[name(5, 6)] - 2, 3」となります。
BracketedPoint point1("(", ")", new PlainPoint(5, 6)); NamedPoint namedPoint("name", point1.clone()); BracketedPoint bracketedPoint("[", "]", namedPoint.clone()); PlainPoint point2(2, 3); Line line(bracketedPoint.clone(), point2.clone()); cout << line.toString() << endl;
ちなみに、Line クラスも今回と同様の方法で拡張していくことができます。