第8回

カプセル化

クラス内部のデータを適切に隠し、外部から安全に操作する。

public

class Motor {
public:
    void setSpeed(int speed);
};

外部から呼び出せる。

private

class Motor {
private:
    int speed;
};

外部から直接アクセスできない。

protected

class Sensor {
protected:
    int value;
};

通常の外部コードからはアクセスできないが、派生クラスから利用できる。

アクセス範囲

修飾子クラス内部派生クラス外部
public○○○
protected○○×
private○××

getter / setter

class Motor {
public:
    void setSpeed(int speed);
    int getSpeed() const;

private:
    int speed;
};

なぜprivateにするのか

例えば、

motor.speed = -100000;

のような不正な値を外部から設定される可能性がある。そこで、

void Motor::setSpeed(int speed) {
    if (speed < 0) {
        speed = 0;
    }

    if (speed > 100) {
        speed = 100;
    }

    this->speed = speed;
}

のようにクラス内部で制限する。値を守る場所が1か所に集まるため、不正な状態が作られにくくなる。

カプセル化の本質

単に「privateを使う」ということではない。

内部の実装
    ↓
隠す

外部には
    ↓
必要な操作だけ公開する

という考え方を理解する。

動かして確かめる

演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/08-motor-encapsulation/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。

code/examples/08-motor-encapsulation/
├── include/
│   └── Motor.hpp    public に見せるのは setSpeed / getSpeed / stop だけ
└── src/
    ├── Motor.cpp    範囲の制限と PWM への変換はここに閉じている
    └── main.cpp
code/examples/08-motor-encapsulation/src/main.cpp
#include <Arduino.h>

#include "Motor.hpp"

// モータドライバはつないでいない。結果はシリアルの出力で確かめる
Motor motor(24);

void setup() {
    Serial.begin(115200);
    motor.begin();

    // 範囲外の値を渡しても内部で丸められる
    motor.setSpeed(-100000);
    Serial.println(motor.getSpeed()); // 0

    motor.setSpeed(500);
    Serial.println(motor.getSpeed()); // 100
}

void loop() {
}
シリアルモニタの出力
0
100

確かめること。

  • setSpeed(-100000)でも0、setSpeed(500)でも100になる。範囲はクラスの内部で守られている
  • speedはprivateなので、motor.speed = 500;とは書けない。試して確かめる
  • apply()もprivate。使う側が呼ぶ必要のない手続きは見せない
  • 範囲の判定をmain.cpp側に書いた場合、モータを使う場所が増えたらどうなるかを考える

演習

  • 第6回のLEDクラス、第7回のMotorクラスのメンバ変数をすべてprivateにする
  • Motor::setSpeed()に0〜100の制限を実装する
  • そのクラスで「外部に本当に必要な操作」は何かを書き出す