C++におけるインターフェース設計
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 61/104。
C++のinterfaceとは、(仮想)destructorを除くすべてのメンバー関数が純粋仮想関数であるabstractクラスです。一部の実装を含むことがあるabstractクラスとは異なり、interfaceは存在しなければならない操作だけを定義します。つまり、動作を持たずに契約を指定します。
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};この Drawable インターフェースは、それを実装する任意の class が draw() メソッドと resize() メソッドを持つことを保証しますが、それらがどのように動作するかについては何も述べていません。この分離は強力です。コードは具体的な型について何も知らなくても、インターフェースに依存できます。
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* ボタンをリサイズ */ }
};クラスは複数のインターフェースを実装できるため、オブジェクトが異なる役割を果たせる柔軟な設計が可能になります。
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};インターフェースは疎結合を促進します。つまり、コードは具体的な実装ではなく抽象化に依存するため、拡張やテストが容易になります。
チャレンジ
簡単さまざまな種類のデバイスに対してインターフェースがどのように契約を定義するかを示す、デバイス管理システムを構築しましょう。デバイスが実装できる2つの別々のインターフェースを作成し、その後、これらの契約の一方または両方を満たす具体的なデバイスクラスを構築します。
コードを3つのファイルに分けて整理します。
Interfaces.h:異なるデバイスの機能を表す、2つの純粋仮想クラス(インターフェース)を定義します。Powerable:電源をオン・オフできる任意のデバイス:- 純粋仮想
powerOn()メソッド - 純粋仮想
powerOff()メソッド std::stringを返す純粋仮想getPowerStatus()メソッド- 仮想デストラクタ
Connectable:ネットワークに接続できる任意のデバイス:- 純粋仮想
connect(const std::string& network)メソッド - 純粋仮想
disconnect()メソッド std::stringを返す純粋仮想getConnectionInfo()メソッド- 仮想デストラクタ
- 純粋仮想
Devices.h:これらのインターフェースを使用する具体的なデバイスクラスを実装します。Lamp:Powerableのみを実装します:- private な
bool isOnメンバー(初期値はfalse) - private な
std::string nameメンバー - ランプの名前を受け取るコンストラクタ
powerOn()はisOnを true に設定し、次を出力します:<name>: Light turned onpowerOff()はisOnを false に設定し、次を出力します:<name>: Light turned offgetPowerStatus()は状態に応じて"ON"または"OFF"を返します
SmartTV:PowerableとConnectableの両方を実装します:- private メンバー:
bool isOn(初期値は false)、std::string brand、std::string currentNetwork(初期値は空) - テレビのブランドを受け取るコンストラクタ
powerOn()はisOnを true に設定し、次を出力します:<brand> TV: Powered onpowerOff()はisOnを false に設定し、ネットワークをクリアして、次を出力します:<brand> TV: Powered offgetPowerStatus()は"ON"または"OFF"を返しますconnect()はネットワーク名を保存し、次を出力します:<brand> TV: Connected to <network>disconnect()はネットワークをクリアし、次を出力します:<brand> TV: DisconnectedgetConnectionInfo()は接続されている場合は"Connected to <network>"を、空の場合は"Not connected"を返します
- private な
main.cpp:3つの入力を読み取ります(それぞれ別の行に入力します):- ランプ名
- テレビのブランド
- ネットワーク名
LampとSmartTVを作成します。同じインターフェースを異なるデバイスで使用できることを示します。まず、
Powerableインターフェースを通じて両方のデバイスを操作します。Powerable*の配列に両方へのポインターを保存し、ループで各デバイスに対してpowerOn()を呼び出した後、ステータスを次の形式で出力します:Status: <powerStatus>空行を出力し、次に
Connectableインターフェースを通じて SmartTV を操作します。SmartTV へのConnectable*ポインターを作成し、ネットワーク名を使ってconnect()を呼び出し、次を出力します:Connection: <connectionInfo>もう一度空行を出力し、次に
Powerableの配列を通じて両方のデバイスの電源をオフにし、最終的なステータスを表示します。
たとえば、入力が Desk Lamp、Samsung、HomeWiFi の場合:
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFFSmartTV は、使用するインターフェースポインターに応じて、Powerable または Connectable のいずれかとして扱えることに注目してください。この柔軟性こそが、複数のインターフェースを実装する利点です。具体的な型を知らなくても、コードは必要な契約を満たす任意のデバイスと連携できます。
自分で試してみよう
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// 入力を読み取る
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Lamp と SmartTV のオブジェクトを作成する
// TODO: 両方のデバイスを含む Powerable* ポインタの配列を作成する
// TODO: 配列をループして各デバイスで powerOn() を呼び出す
// 各 powerOn() の後に、Status: <powerStatus> を出力する
// TODO: 空行を出力する
// TODO: SmartTV への Connectable* ポインタを作成する
// ネットワーク名を指定して connect() を呼び出す
// Print: Connection: <connectionInfo>
// TODO: 空行を出力する
// TODO: Powerable 配列をループして各デバイスで powerOff() を呼び出す
// 各 powerOff() の後に、Status: <powerStatus> を出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ