SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

特集記事

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

拡張/変更を考慮した構造

 あらためてもう一度、今度は拡張/変更を考慮した構造に仕立てましょう(C#,VB,Java版はおマケのサンプルファイルに納めてあります)。インターフェース名は.NETの流儀に則り'I'を頭に付けることにします。

 まずVisitorおよびVisitorの訪問を受理するElementをインターフェースとします:

list-06
class IVisitor {
public:
  virtual ~IVisitor() {}
};

class IElement {
public:
  virtual void accept(IVisitor& visitor) =0;
};

 脱力するほどナカミのないこの2つをベースにVisitorパターンを組み上げましょう。サンプルに用いるのは「自作PCのお値段調べ」です。僕の愛用するマシンは、ざっくりこんな部品構成になっています:

  Case
    |
    +-- PowerSupply
    |
    +-- MotherBoard
          |
          +-- CPU
          |
          +-- Memory(n枚)

 Case内にPowerSupplyとMotherBoard、MotherBoardはCPUとMemoryを内包しています。各部品には名前と価格が付いていて、総額おいくらになるのかをVisitorパターンで求めてみます。

 Equipment(部品)およびCompositeElement(他の部品を内包する部品)を定義します。前述の例ではShopとShoppingmallに相当しますね:

list-07
class Equipment : virtual public IElement {
public:
  Equipment(const string& name, int price) 
    : name_(name), price_(price) {}

  virtual ~Equipment() {}

  virtual string name()  const { return name_; }
          int    price() const { return price_; }

  virtual void accept(IVisitor& visitor) =0;

private:
  string name_;
  int    price_;
};

class CompositeEquipment : public Equipment {
public:
  CompositeEquipment(const string& name, int price)
    : Equipment(name,price) {}

  void add(Equipment* eq) {
    contains_.push_back(unique_ptr<Equipment>(eq));
  }

  virtual void accept(IVisitor& visitor) {
    for_each(contains_.begin(), contains_.end(),
      [&](unique_ptr<Equipment>& eq) { eq->accept(visitor); });
  }

private:
  vector<unique_ptr<Equipment>> contains_;
};

 EquipmentからPowersupply,CPU,Memoryを、CompositeEquipmentからCaseとMotherBoardを導出します。

list-08
class CPU : public Equipment {
public:
  CPU(const string& name, int price)
    : Equipment(name,price) {}

  virtual string name() const {
    return "CPU: " + Equipment::name();
  }

  virtual void accept(IVisitor& visitor) {
    dynamic_cast<IEquipmentVisitor&>(visitor).visitCPU(this);
  }
};

// PowerSupply, Memory は CPUと同様なので省略

class Case : public CompositeEquipment {
public:
  Case(const string& name, int price)
    : CompositeEquipment(name,price) {}

  virtual string name() const {
    return "Case: " + CompositeEquipment::name();
  }

  virtual void accept(IVisitor& visitor) {
    dynamic_cast<IEquipmentVisitor&>(visitor).visitCase(this);
  }
};

// MotherBoard は Caseと同様なので省略

 各部品を訪ねて回るVisitorのインターフェースをIEquipmentVisitorとしましょうか。

list-09
class IEquipmentVisitor : virtual public IVisitor {
public:
  virtual void visitCPU(CPU*) =0;
  virtual void visitMotherBoard(MotherBoard*) =0;
  virtual void visitCase(Case*) =0;
  virtual void visitMemory(Memory*) =0;
};

 各部品を訪れてprice()の返す価格を積み上げるPricingVisitorはもちろんIEquipmentVisitorを実装することになりますな:

list-10
class PricingVisitor : virtual public IEquipmentVisitor {
public:
  PricingVisitor() : total_(0) {}
  int totalPrice() const { return otal_; }
  virtual void visitCPU(CPU* eq) { accumulate(eq->price(); }
  virtual void visitMotherBoard(MotherBoard* eq) { accumulate(eq->price(); }
  virtual void visitCase(Case* eq) { accumulate(eq->price(); }
  virtual void visitMemory(Memory* eq) { accumulate(eq->price(); }
protected:
  void accumulate(int price);
private:
  int total_;
};

 これででき上がり。PCを組み立て、総額おいくらになるかPricingVisitorに集計させます:

list-11
int main() {
  Case pc("Lian Lee CX200", 10000);
  MotherBoard* mb = new MotherBoard("GYGABYTE Z68", 9800);
  mb->add(new CPU("Intel i7-2600K", 27000));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));

  PricingVisitor visitor;
  pc.accept(visitor);
  cout << "total price \\" << visitor.totalPrice() << endl;
}
  

 ...おっといけねぇ、ストレージデバイス:HDとDVDを忘れてます。IEquipmentVisitor,PricingVisitorに手を加えることなく、HDとDVDに対応するVisitorを用意することで対応できます:

list-12
class HD;
class DVD;

// 追加分(HD,DVD)のVisitor
class IStorageVisitor : virtual public IVisitor {
public:
  virtual void acceptHD(HD*) =0;
  virtual void acceptDVD(DVD*) =0;
};

class HD : public Equipment {
public:
  ...
  virtual void accept(IVisitor& visitor) {
    dynamic_cast<IStorageVisitor&>(visitor).visitHD(this);
  }
};

// DVD は HDと同様なので省略

// 追加分(HD,DVD)も勘定するPricingVisitor
class PricingVisitorEx : public PricingVisitor, virtual public IStorageVisitor {
public:
  virtual void visitHD(HD* eq)   { accumulate(eq->price()); }
  virtual void visitDVD(DVD* eq) { accumulate(eq->price()); }
};

int main() {
  Case pc("Lian Lee CX200", 10000);
  MotherBoard* mb = new MotherBoard("GYGABYTE Z68", 9800);
  mb->add(new CPU("Intel i7-2600K", 27000));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  mb->add(new Memory("Kingston 4GB", 4800, 4));
  pc.add(mb);
  pc.add(new HD("SAMSUNG 1TB", 7800, 1000));
  pc.add(new DVD("SonyOptiarc super-multi", 2800));

  PricingVisitorEx visitor;
  pc.accept(visitor);
  cout << "total price \\" << visitor.totalPrice() << endl;
}

 登場したクラスの関係をまとめた図を示します。

 IElementを頂点とした構造と、IVisitorを頂点とした操作とがキレイに分離されてますね。これでどうにかエラソーな顔して1時間のレクチャができそうです。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

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

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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー