Menu
Coddy logo textTech

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 インターフェースは、それを実装する任意の classdraw() メソッドと 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; }
};

インターフェースは疎結合を促進します。つまり、コードは具体的な実装ではなく抽象化に依存するため、拡張やテストが容易になります。

challenge icon

チャレンジ

簡単

さまざまな種類のデバイスに対してインターフェースがどのように契約を定義するかを示す、デバイス管理システムを構築しましょう。デバイスが実装できる2つの別々のインターフェースを作成し、その後、これらの契約の一方または両方を満たす具体的なデバイスクラスを構築します。

コードを3つのファイルに分けて整理します。

  • Interfaces.h:異なるデバイスの機能を表す、2つの純粋仮想クラス(インターフェース)を定義します。

    Powerable:電源をオン・オフできる任意のデバイス:

    • 純粋仮想 powerOn() メソッド
    • 純粋仮想 powerOff() メソッド
    • std::string を返す純粋仮想 getPowerStatus() メソッド
    • 仮想デストラクタ

    Connectable:ネットワークに接続できる任意のデバイス:

    • 純粋仮想 connect(const std::string& network) メソッド
    • 純粋仮想 disconnect() メソッド
    • std::string を返す純粋仮想 getConnectionInfo() メソッド
    • 仮想デストラクタ
  • Devices.h:これらのインターフェースを使用する具体的なデバイスクラスを実装します。

    LampPowerable のみを実装します:

    • private な bool isOn メンバー(初期値は false
    • private な std::string name メンバー
    • ランプの名前を受け取るコンストラクタ
    • powerOn()isOn を true に設定し、次を出力します:<name>: Light turned on
    • powerOff()isOn を false に設定し、次を出力します:<name>: Light turned off
    • getPowerStatus() は状態に応じて "ON" または "OFF" を返します

    SmartTVPowerableConnectable の両方を実装します:

    • private メンバー:bool isOn(初期値は false)、std::string brandstd::string currentNetwork(初期値は空)
    • テレビのブランドを受け取るコンストラクタ
    • powerOn()isOn を true に設定し、次を出力します:<brand> TV: Powered on
    • powerOff()isOn を false に設定し、ネットワークをクリアして、次を出力します:<brand> TV: Powered off
    • getPowerStatus()"ON" または "OFF" を返します
    • connect() はネットワーク名を保存し、次を出力します:<brand> TV: Connected to <network>
    • disconnect() はネットワークをクリアし、次を出力します:<brand> TV: Disconnected
    • getConnectionInfo() は接続されている場合は "Connected to <network>" を、空の場合は "Not connected" を返します
  • main.cpp:3つの入力を読み取ります(それぞれ別の行に入力します):
    1. ランプ名
    2. テレビのブランド
    3. ネットワーク名

    LampSmartTV を作成します。同じインターフェースを異なるデバイスで使用できることを示します。

    まず、Powerable インターフェースを通じて両方のデバイスを操作します。Powerable* の配列に両方へのポインターを保存し、ループで各デバイスに対して powerOn() を呼び出した後、ステータスを次の形式で出力します:Status: <powerStatus>

    空行を出力し、次に Connectable インターフェースを通じて SmartTV を操作します。SmartTV への Connectable* ポインターを作成し、ネットワーク名を使って connect() を呼び出し、次を出力します:Connection: <connectionInfo>

    もう一度空行を出力し、次に Powerable の配列を通じて両方のデバイスの電源をオフにし、最終的なステータスを表示します。

たとえば、入力が Desk LampSamsungHomeWiFi の場合:

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() を呼び出す
    // Print: Connection: <connectionInfo>
    
    // TODO: 空行を出力する
    
    // TODO: Powerable 配列をループして各デバイスで powerOff() を呼び出す
    // 各 powerOff() の後に、Status: <powerStatus> を出力する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ