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