第14回 解答例

責務を割り当てる

演習の解答例。まず自分で書いてから読むこと。

1. RobotStateは誰が持つべきか

候補はRobotとControllerの2つ。

持たせる先根拠評価
Robotロボット全体の状態だからRobotが状態遷移の判断まで持つと肥大化する
Controller状態を決めるのは制御判断だから推奨。状態と、状態を変える処理が同じ場所にある

判断の根拠は「そのデータを変更する処理はどこにあるか」である。状態を変えるのは「距離が近いから止まろう」という制御判断であり、それはControllerの仕事。データは、それを変更する処理と同じクラスに置く。

class Controller {
public:
    void update();
    RobotState getState() const;   // 外からは読むだけ

private:
    RobotState state;              // 変更できるのは Controller だけ
};

2. 「距離が近ければ止まる」は誰の責務か

この処理は2つに分解できる。

処理担当理由
距離を測って数値にするDistanceSensorセンサの読み取り方はセンサが知っていればよい
「近い」と判断して止めるControllerしきい値も停止の判断も制御の都合

DistanceSensorにisNear()を持たせたくなるが、勧められない。

// 考えどころ
class DistanceSensor {
public:
    bool isNear() const;   // 「近い」の基準は誰が決める?
};

「近い」の基準は用途によって変わる。小型ロボットなら100mm、大型なら1000mmかもしれない。センサは「何mmか」だけを答え、それを近いと見なすかはControllerが決める。

// 推奨
void Controller::update() {
    if (distance.getDistanceMm() < STOP_DISTANCE_MM) {
        state = RobotState::STOP;
        motor.stop();
    } else {
        state = RobotState::MOVE;
        motor.setSpeed(CRUISE_SPEED);
    }
}

3. 悪い例を分割し直す

元のFlightControllerは、変更理由が8つある。

FlightController
 ├── IMU読み取り      センサを変えたら直す
 ├── GPS読み取り      センサを変えたら直す
 ├── PID計算          制御則を変えたら直す
 ├── サーボ制御       機体構成を変えたら直す
 ├── モータ制御       機体構成を変えたら直す
 ├── WiFi             通信方式を変えたら直す
 ├── ログ             記録先を変えたら直す
 └── 状態管理         飛行モードを増やしたら直す

分割の基準は「変更理由が同じものを1つにまとめる」である。

FlightController          ← まとめ役だけを担当
 │
 ├── IMU                  ┐
 ├── GPS                  ┘ センサ(Sensorを継承)
 │
 ├── PIDController        制御計算
 │
 ├── ServoManager         ┐
 ├── MotorManager         ┘ アクチュエータ
 │
 └── FlightState          状態管理

FlightControllerに残るのは「各部品を順番に動かす」ことだけになる。

void FlightController::update() {
    for (int i = 0; i < SENSOR_COUNT; i++) {
        sensors[i]->update();
    }

    float command = pid.compute(imu.getRoll(), target);

    servos.apply(command);
}

判断に迷ったときの問い

  • このクラスは一言で何をするものか説明できるか
  • このクラスを直す理由は何通りあるか(多ければ分割)
  • このデータを変更する処理はどこにあるか(そこに置く)
  • この判断基準は、使う側によって変わるか(変わるなら上位に置く)

→ 第14回の本文に戻る