第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);
}判断に迷ったときの問い
- このクラスは一言で何をするものか説明できるか
- このクラスを直す理由は何通りあるか(多ければ分割)
- このデータを変更する処理はどこにあるか(そこに置く)
- この判断基準は、使う側によって変わるか(変わるなら上位に置く)