C++におけるインターフェース設計
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 61/104。
C++におけるインターフェースとは、(仮想)デストラクタを除くすべてのメンバ関数が純粋仮想関数である抽象クラスのことです。一部の実装を含むことがある抽象クラスとは異なり、インターフェースはどのような操作が存在しなければならないかのみを定義します。つまり、振る舞いを伴わずに契約を指定するものです。
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};この Drawable インターフェースは、実装するすべてのクラスが 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(初期値は空) - TVのブランド名を受け取るコンストラクタ
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つの入力(それぞれ別の行)を読み込みます:- ランプの名前
- TVのブランド名
- ネットワーク名
LampとSmartTVを作成します。同じインターフェースを異なるデバイスでどのように使用できるかを示します:まず、
Powerableインターフェースを通じて両方のデバイスを操作します。両方のポインタをPowerable*の配列に格納し、ループを回してそれぞれに対してpowerOn()を呼び出し、続いてそのステータスをStatus: <powerStatus>として出力します。空行を1行出力し、次に
Connectableインターフェースを通じて SmartTV を操作します。SmartTV へのConnectable*ポインタを作成し、ネットワーク名でconnect()を呼び出し、Connection: <connectionInfo>と出力します。さらにもう1行空行を出力し、
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: OFF使用するインターフェースポインタによって、SmartTV が 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()を呼び出す
// Connection: <connectionInfo> を出力する
// TODO: 空行を出力する
// TODO: Powerable配列をループし、各デバイスでpowerOff()を呼び出す
// 各powerOff()の後に、Status: <powerStatus> を出力する
return 0;
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
8ポリモーフィズム
コンパイル時 vs 実行時ポリモーフィズム関数オーバーロード仮想関数の再考純粋仮想関数抽象クラスC++におけるインターフェース設計Dynamic Casting & RTTIまとめ:図形計算機3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス