はじめに
社内の設計/開発者を対象とした1時間レクチャの依頼が舞い込みました。お題はなにかと尋ねると「デザイン・パターン」とのこと。いやそれは無理。20個以上のデザイン・パターンを1時間で解説なんて無茶ブリにも程があるってもんです。全体像を軽く流し、サンプルとしてパターンを1つ紹介するってことで勘弁してもらいました。「それじゃVisitorパターンでよろしく」ですって...んー、Visitorパターンは使ったことないなー...触ったことのないものをエラソーに語るほど肝がすわってはいませんから、週末に本読んで資料集めてサンプル・コードを書いてPowerPointいじっていました。そんなわけで今回はVisitorパターンのご紹介。
Visitorパターン
デザイン・パターンといえばバイブルGoF本。Visitorは「振る舞いに関するパターン」の一つとしてGoF本の最後に置かれています。手垢で汚れた僕のGoF本ですがVisitorの項はそんなに汚れてない、今までVisitorを使う機会がなかったんですね。
Visitor:直訳すれば「来客」とか「訪問者」って意味ですけど、僕のイメージではErrand:「おつかい」の方がしっくりきます。
ちょっとコード書きますね。例えば「パン屋さん」と「郵便局」:
// パン屋 class Bakery : public Shop { public: void get_bread() { cout << "Bekery::get_bread : 毎度ありー♪\n"; } }; // 郵便局 class Postoffice : public Shop { public: void post_letter() { cout << "Postoffice::post_letter : 承りました\n"; } };
それぞれ"パンを買う","手紙を投函する"メソッドを提供しています。
ここで「おつかい」を定義します。おつかいはパン屋/郵便局に行ったら何をしなきゃいけないかを知っています:
// おつかい class Errand { public: void visitBakery(Bakery* b) { b->get_bread(); } void visitPostoffice(Postoffice* p) { p->post_letter(); } };
いろんなお店でおつかいできるよう少しばかり書き直し。おつかい用のインターフェースを持った抽象ベースクラス:Shopを用意し、お店はShopから導出します:
class Errand; class Shop { public: virtual ~Shop() {} virtual void accept(Errand&) =0; }; class Bakery : public Shop { public: virtual void accept(Errand&); void get_bread() { cout << "Bekery::get_bread : 毎度ありー♪\n"; } }; class Postoffice : public Shop { public: virtual void accept(Errand&); void post_letter() { cout << "Postoffice::post_letter : 承りました\n"; } }; class Errand { public: void visitBakery(Bakery* b) { b->get_bread(); } void visitPostoffice(Postoffice* p) { p->post_letter(); } }; // 各店のacceptはそれぞれに適したErrand::visitXXXを呼ぶ void Bakery::accept(Errand& errand) { errand.visitBakery(this); } void Postoffice::accept(Errand& errand) { errand.visitPostoffice(this); }
こうしておけばErrandはShopから導出したさまざまなお店で頼まれた用事を済ますことができますね:
int main() { Bakery b; Postoffice p; Errand e; Shop* s; s = &b; s->accept(e); // パン屋におつかい s = &p; s->accept(e); // 郵便局におつかい }
少しばかりヒネったお店、Shopの集合体である商店街:Shoppingmallでおつかいさせてみましょう:
// 商店街 class Shoppingmall : public Shop { public: virtual void accept(Errand& errand) { // 商店街にある各店舗におつかいに行かせる for_each(begin(shops_), end(shops_), [&](Shop* s) { s->accept(errand); }); } virtual ~Shoppingmall() { for_each(begin(shops_), end(shops_), [&](Shop* s) { delete s; }); } void add_shop(Shop* s) { shops_.push_back(s); } private: vector<Shop*> shops_; }; int main() { Shoppingmall m; m.add_shop(new Bakery()); m.add_shop(new Postoffice()); Errand e; m.accept(e); // 商店街におつかい }
このとき、商店街の各店をおつかいに回らせる責任は商店街にあります。おつかいに来た人はどの店をどんな順序で回るかを知りません。対しておつかいに来た人が各店でなにをするか(visitXXX内の処理)はお店の関知することじゃありません。つまり「構造」と「操作」を分離しているわけ。これこそがVisitorパターンのキモです。visitorパターンを使うことで、構造は操作を/操作は構造を気にせずに双方を変更することができます。クラス図はこんなカンジかな。