squirrel_code @ ウィキ
Double dispatch がめんどくさい
最終更新:
squirrel_code
-
view
Double dispatch がめんどくさい
last update: 2013/09/04 (Wed)
ヤブから棒ではあるが,
Rectangle rectangle; Circle circle; PenSVG pen_svg; PenPNG pen_png; rectangle.drawWith(pen_svg); circle.drawWith(pen_svg); rectangle.drawWith(pen_png); circle.drawWith(pen_png);
…というようなことがしたい.要は四角形とか円とか,ひょっとしたら楕円とか菱形とかいろんな図形があって, PNG とか SVG とか,ひょっとしたら JPEG とかに出力するペンがあって,どの図形も,どのペンでも自在に組み合わせて描きたい,という意味である.
その1
簡単にプログラムを書くとこんな感じ.
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Rectangle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
class Circle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
#include <iostream>
void
Rectangle::drawWith(PenToPNG & pen)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
Rectangle::drawWith(PenToSVG & pen)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
Circle::drawWith(PenToPNG & pen)
{
std::cout << "Circle: PNG" << std::endl;
}
void
Circle::drawWith(PenToSVG & pen)
{
std::cout << "Circle: SVG" << std::endl;
}
// Pen.hpp
class PenToPNG
{
};
class PenToSVG
{
};
// Pen.cpp // no line
画像フォーマット云々に踏み込むつもりはないので,関数の中身はテキスト出力で代用した.たった2種類の図形,2種類のペンではあるが,なかなかに長い.そして似たような行が延々と並ぶ.そして図形の種類やペンの種類を増やすときはとりあえず 4yy C-w l p C-v jjj :'<,'>s/Rectangle/Triangle/g ...
なんてことは間違ってもしてはいけない…のだが,それ以前に SVG とか PNG とか,特定の画像フォーマットに依存するコードが図形クラスの中で実装してあるのがなんとも気持ちが悪い.まずはここをなんとかしよう.
その2
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Rectangle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
class Circle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(PenToPNG & pen)
{
pen.draw(*this);
}
void
Rectangle::drawWith(PenToSVG & pen)
{
pen.draw(*this);
}
void
Circle::drawWith(PenToPNG & pen)
{
pen.draw(*this);
}
void
Circle::drawWith(PenToSVG & pen)
{
pen.draw(*this);
}
// Pen.hpp
class Rectangle;
class Circle;
class PenToPNG
{
public:
void
draw(Rectangle & rectangle);
void
draw(Circle & rectangle);
};
class PenToSVG
{
public:
void
draw(Rectangle & rectangle);
void
draw(Circle & rectangle);
};
// Pen.cpp
#include "Pen.hpp"
#include "Shape.hpp"
#include <iostream>
void
PenToPNG::draw(Rectangle & rectangle)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
PenToPNG::draw(Circle & rectangle)
{
std::cout << "Circle: PNG" << std::endl;
}
void
PenToSVG::draw(Rectangle & rectangle)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
PenToSVG::draw(Circle & rectangle)
{
std::cout << "Circle: SVG" << std::endl;
}
これで画像の詳細に関わる実装がペンクラスに移動した.さて,Rectangle も Circle も drawWith メソッドを持ち,PenToSVG も PenToPNG も draw メソッドを持っている.同じ操作を受け付けるということは,基底クラスを作ってやることで,ぽりもーふぃっく(ひらがな)できる可能性があるわけだ.そこで「一般の図形」を表すクラス Shape と一般のペンを表すクラス Pen をつくり, Rectangle と Circle を Shape の, PenToSVG と PenToPNG を Pen の派生クラスにしてみよう.ついでにリファレンスもポインタに変えておく.
その3
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Shape
{
public:
virtual
~Shape()
{ }
virtual void
drawWith(PenToPNG * pen) = 0;
virtual void
drawWith(PenToSVG * pen) = 0;
};
class Rectangle
: public Shape
{
public:
virtual void
drawWith(PenToPNG * pen) final override;
virtual void
drawWith(PenToSVG * pen) final override;
};
class Circle
: public Shape
{
public:
virtual void
drawWith(PenToPNG * pen) final override;
virtual void
drawWith(PenToSVG * pen) final override;
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(PenToPNG * pen)
{
pen->draw(this);
}
void
Rectangle::drawWith(PenToSVG * pen)
{
pen->draw(this);
}
void
Circle::drawWith(PenToPNG * pen)
{
pen->draw(this);
}
void
Circle::drawWith(PenToSVG * pen)
{
pen->draw(this);
}
// Pen.hpp
class Rectangle;
class Circle;
class Pen
{
public:
virtual
~Pen()
{ }
virtual void
draw(Rectangle * rectangle) = 0;
virtual void
draw(Circle * rectangle) = 0;
};
class PenToPNG
: public Pen
{
public:
virtual void
draw(Rectangle * rectangle) final override;
virtual void
draw(Circle * rectangle) final override;
};
class PenToSVG
: public Pen
{
public:
virtual void
draw(Rectangle * rectangle) final override;
virtual void
draw(Circle * rectangle) final override;
};
// Pen.cpp
#include "Pen.hpp"
#include "Shape.hpp"
#include <iostream>
void
PenToPNG::draw(Rectangle * rectangle)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
PenToPNG::draw(Circle * rectangle)
{
std::cout << "Circle: PNG" << std::endl;
}
void
PenToSVG::draw(Rectangle * rectangle)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
PenToSVG::draw(Circle * rectangle)
{
std::cout << "Circle: SVG" << std::endl;
}
見ればわかるが, Shape クラスは多少簡単にすることができる.
// Shape.hpp
class Pen;
class Shape
{
public:
virtual void
drawWith(Pen * pen) = 0;
};
class Rectangle
: public Shape
{
public:
virtual void
drawWith(Pen * pen) final override;
};
class Circle
: public Shape
{
public:
virtual void
drawWith(Pen * pen) final override;
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(Pen * pen)
{
pen->draw(this);
}
void
Circle::drawWith(Pen * pen)
{
pen->draw(this);
}
使うときはこんな感じ.
Shape * rectangle = new Rectangle(); Shape * circle = new Circle(); Pen * pen_svg = new PenToSVG(); Pen * pen_png = new PenToPNG(); rectangle->drawWith(pen_svg); circle->drawWith(pen_svg); rectangle->drawWith(pen_png); circle->drawWith(pen_png);
これで Shape::drawWith の呼び出しで自動的にそれぞれの図形やペンに対応した処理がなされる.これを条件分岐で実装すると if 文がネストして大変なことになるが,クラス間をたらいまわしにするこの手のテクを使えば美しく実装することができる.これを「ダブルディスパッチ」と呼ぶ.
つづく.
&trackback()
参考
コメント
- コメントの投稿テスト -- (tossy_squirrel) 2010-12-29 03:35:18