SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Visitorパターンで遊んでみたよ

  • このエントリーをはてなブックマークに追加

 デザイン・パターンのひとつ:Visitorの解説。C++,C#,VB.NET,Javaのサンプル・コードを用意しました。

  • このエントリーをはてなブックマークに追加

はじめに

 社内の設計/開発者を対象とした1時間レクチャの依頼が舞い込みました。お題はなにかと尋ねると「デザイン・パターン」とのこと。いやそれは無理。20個以上のデザイン・パターンを1時間で解説なんて無茶ブリにも程があるってもんです。全体像を軽く流し、サンプルとしてパターンを1つ紹介するってことで勘弁してもらいました。「それじゃVisitorパターンでよろしく」ですって...んー、Visitorパターンは使ったことないなー...触ったことのないものをエラソーに語るほど肝がすわってはいませんから、週末に本読んで資料集めてサンプル・コードを書いてPowerPointいじっていました。そんなわけで今回はVisitorパターンのご紹介。

Visitorパターン

 デザイン・パターンといえばバイブルGoF本。Visitorは「振る舞いに関するパターン」の一つとしてGoF本の最後に置かれています。手垢で汚れた僕のGoF本ですがVisitorの項はそんなに汚れてない、今までVisitorを使う機会がなかったんですね。

 Visitor:直訳すれば「来客」とか「訪問者」って意味ですけど、僕のイメージではErrand:「おつかい」の方がしっくりきます。

 ちょっとコード書きますね。例えば「パン屋さん」と「郵便局」:

list-01
// パン屋
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"; }
};

 それぞれ"パンを買う","手紙を投函する"メソッドを提供しています。

 ここで「おつかい」を定義します。おつかいはパン屋/郵便局に行ったら何をしなきゃいけないかを知っています:

list-02
// おつかい
class Errand {
public:
  void visitBakery(Bakery* b) { b->get_bread(); } 
  void visitPostoffice(Postoffice* p) { p->post_letter(); }
};

 いろんなお店でおつかいできるよう少しばかり書き直し。おつかい用のインターフェースを持った抽象ベースクラス:Shopを用意し、お店はShopから導出します:

list-03
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から導出したさまざまなお店で頼まれた用事を済ますことができますね:

list-04
int main() {
  Bakery b;
  Postoffice p;
  Errand e;
  Shop* s;
  s = &b;
  s->accept(e); // パン屋におつかい
  s = &p;
  s->accept(e); // 郵便局におつかい
}

 少しばかりヒネったお店、Shopの集合体である商店街:Shoppingmallでおつかいさせてみましょう:

list-05
// 商店街
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パターンを使うことで、構造は操作を/操作は構造を気にせずに双方を変更することができます。クラス図はこんなカンジかな。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
拡張/変更を考慮した構造

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6829 2012/10/31 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング