#35 仮想関数の代わりになるものを考える

Effective C++ #35 の内容です.この項はちょっと一風変わっていて,継承と仮想関数の仕組みを用いたシンプルなポリモーフィズムの実装を,異なるデザインパターンでおきかえる話でした.登場したデザインパターンとしては,

1.非仮想のインターフェースを使うテンプレートメソッドパターン
2.関数ポインタを使うストラテジパターン
3.tr1::functionによるストラテジパターン
4.古典的なストラテジパターン

の4つでした.サンプルコードを通して,4つのパターンを見ていきたいと思います.

0.シンプルに仮想関数を使って実装してみる.

下記,Lidarを持つロボットとCameraを持つロボットで環境認識の方法が変わるので,Robotという基底クラスに対してLidarRobot, CameraRobotというクラスを派生させています.う〜ん.我ながら,このクラス設計はいけてないですね(笑).ストラテジパターンを使えばうまくまとまるのかしら.

#include <iostream>

class Robot {
public:
  virtual ~Robot(){}
  virtual double sense() const = 0;
};

class CameraRobot : public Robot {
public:
  virtual ~CameraRobot(){}
  virtual double sense() const {
    std::cout << "Sensing by Camera." << std::endl;
    return 0.0;
  };
};

class LidarRobot : public Robot {
public:
  virtual ~LidarRobot(){};
  virtual double sense() const {
    std::cout << "Sensing by Lidar." << std::endl;
    return 0.0;
  };
};

int main(int, char const **)
{

  const Robot* robo = new CameraRobot();
  robo->sense();

  return 0;
}

1.非仮想のインターフェースを使うテンプレートメソッドパターン.

次に,非仮想のインターフェースをパターンですが,こいつは0のケースとそんなに変わってないですね.virtualな関数をprivateにして,上からpublicのテンプレートメソッドから呼ぶようにしただけです.仮想関数はprivateで有るべきという考え方が元になっているパターンのようで,NVI(Non-virtual interface)イディオムとよぶらしいです.

#include <iostream>

class Robot {
public:
  virtual ~Robot(){}
  double sense() const {
    return this->doSense();
  }

private:
  virtual double doSense() const = 0;
};

class CameraRobot : public Robot {
public:
  virtual ~CameraRobot(){}
private:
  virtual double doSense() const {
    std::cout << "Sensing by Camera." << std::endl;
    return 0.0;
  };
};

class LidarRobot : public Robot {
public:
  virtual ~LidarRobot(){};
private:
  virtual double doSense() const {
    std::cout << "Sensing by Lidar." << std::endl;
    return 0.0;
  };
};

int main(int, char const **)
{

  std::cout << "Hello World!" << std::endl;

  const Robot* robo = new CameraRobot();
  robo->sense();


  return 0;
}

2.関数ポインタを使うストラテジパターン.

ここからの3つはストラテジパターンです.おそらくこの例のパターンはストラテジパターンとして実装すべき内容なのだと思います.


#include <iostream>

double cameraSense() {
  std::cout << "CameraSensing" << std::endl;
  return 0.0;
}

double lidarSense() {
  std::cout << "LidarSensing" << std::endl;
  return 0.0;
}

class Robot {
public:
  typedef double (*senseFunc)(void);
  explicit Robot(senseFunc func) 
  : func(func)
  {}
  double sense() const {
    return this->func();
  }
private:
  senseFunc func;
};

class Humanoid : public Robot {
public:
  explicit Humanoid(senseFunc func) 
    : Robot(func)
  {}
};

int main(int, char**) {

  std::cout << "Hello World!" << std::endl;
  const Robot*robo = new Humanoid(cameraSense);
  robo->sense();

  return 0;
}

3.tr1::functionによるストラテジパターン.

std::tr1::functionはいろんなことができるみたいですね...ちょっとここでやりだすと終わらないので,また別の機会にやります.基本的には,関数ポインタをstd::tr1::functionをつかって表現するように変更されただけです.

#include <iostream>
#include <tr1/functional>

double cameraSense(void) {
  std::cout << "Camera Sense" << std::endl;
  return 0.0;
}

class Robot {
public:
  typedef std::tr1::function<double (void)> senseFunc;
  explicit Robot(senseFunc func) 
  : func(func)
  {};
  virtual ~Robot() {}
  double doSense() const {
    func();
    return 0.0;
  }

private:
  senseFunc func;
};

class Humanoid : public Robot {
public:
  explicit Humanoid(senseFunc func) 
    : Robot(func)
  {}
};

int main(int, char**) {

  std::cout << "Hello World!" << std::endl;
  const Robot* robo = new Humanoid(cameraSense);
  robo->doSense();

  return 0;
}

4.古典的なストラテジパターン.

クラス階層の中の仮想関数を別のクラス階層 ”SensingStrategy” の仮想関数で置き換えました.

#include <iostream>

class SensingStrategy {
public:
  virtual ~SensingStrategy() {}
  virtual double sense() const = 0;
};

class CameraSensing : public SensingStrategy {
public:
  virtual ~CameraSensing() {}
  virtual double sense() const {
    std::cout << "CameraSensing" << std::endl;
    return 0.0;
  }
};

class LidarSensing : public SensingStrategy {
public:
  virtual ~LidarSensing() {}
  virtual double sense() const {
    std::cout << "LidarSensing" << std::endl;
    return 0.0;
  }
};

class Robot {
public:
  explicit Robot(std::string strategy) 
  : sStrat(nullptr)
  {
    if (strategy == "Camera") {
      sStrat = new CameraSensing();
    }
  };
  virtual ~Robot() {}
  double doSense() const {
    sStrat->sense();
    return 0.0;
  }

private:
  SensingStrategy *sStrat;
};

class Humanoid : public Robot {
public:
  explicit Humanoid(std::string strategy) 
    : Robot(strategy)
  {}
};

int main(int, char**) {

  std::cout << "Hello World!" << std::endl;
  const Robot* robo = new Humanoid("Camera");
  robo->doSense();

  return 0;
}