インターフェースの継承

インターフェースの拡張

インターフェースの重複定義

ソフトウェアを開発していくと、既存のインターフェースの機能を持ったまま、新しい機能を追加したインターフェースを作成したい場合があります。 例えば、データの集まりを表す抽象的な IntCollection インターフェースと、順番にデータを保持する IntList インターフェースを定義してみます。

IntListNaive.java

interface IntCollection {
  int size();
  void clear();
}

interface IntList {
  int size();
  void clear();
  void add(int value);
  int get(int index);
}

この方法では、両方のインターフェースに size()clear() メソッドが重複して定義されています。 もし後から「要素数を取得するメソッド名を getSize() に変更しよう」となった場合、複数の箇所を修正しなければならず、バグの原因になります。

extendsキーワードによる継承

このような問題を解決するために、インターフェースは別のインターフェースを継承することができます。 インターフェースを継承するには、extends キーワードを使用します。

IntList.java

interface IntCollection {
  int size();
  void clear();
}

// IntCollectionインターフェースを継承する
interface IntList extends IntCollection {
  void add(int value);
  int get(int index);
}

継承を行うと、親となるインターフェース(IntCollection)が持つメソッドが、子となるインターフェース(IntList)に自動的に引き継がれます。 そのため、IntList インターフェースを実装するクラスは、size()clear()add()get() の4つのメソッドを全て実装する必要があります。

以下のプログラムを実行して、継承したインターフェースがどのように機能するかを確認してみましょう。

ArrayListTest.pde

interface IntCollection {
  int size();
  void clear();
}

interface IntList extends IntCollection {
  void add(int value);
  int get(int index);
}

class ArrayList implements IntList {
  private int[] data = new int[100];
  private int length = 0;

  // IntCollectionから引き継いだメソッドの実装
  public int size() {
    return length;
  }

  public void clear() {
    length = 0;
  }

  // IntList独自のメソッドの実装
  public void add(int value) {
    data[length] = value;
    length++;
  }

  public int get(int index) {
    return data[index];
  }
}

void setup() {
  IntList list = new ArrayList();
  list.add(10);
  list.add(20);

  println("サイズ: " + list.size());
  println("0番目の要素: " + list.get(0));

  list.clear();
  println("クリア後のサイズ: " + list.size());
}

実行結果

サイズ: 2
0番目の要素: 10
クリア後のサイズ: 0

ArrayList クラスは IntList インターフェースのみを実装していますが、親である IntCollection インターフェースのメソッドも正しく実装していることがわかります。

インターフェースの多重継承

複数のインターフェースを継承する

インターフェースは、1つだけでなく複数のインターフェースを同時に継承することができます。 これを多重継承と呼びます。 複数のインターフェースを継承する場合は、extends の後にカンマ(,)で区切ってインターフェース名を列挙します。

データ構造の例として、「スタック(後入れ先出し)」と「キュー(先入れ先出し)」の両方の性質を持つ「両端キュー(デック)」のインターフェースを考えてみましょう。

IntDeque.java

interface IntStack {
  void push(int value);
  int pop();
}

interface IntQueue {
  void enqueue(int value);
  int dequeue();
}

// 2つのインターフェースを継承する
interface IntDeque extends IntStack, IntQueue {
  boolean isEmpty();
}

このように多重継承を活用することで、小さな機能を持ったインターフェースを組み合わせて、より複雑なデータ構造のインターフェースを柔軟に作成することができます。

以下のプログラムを実行して、多重継承したインターフェースの動作を確認してみましょう。 ここでは配列の中央を初期位置とし、前後どちらにもデータを追加できる簡略化した両端キューを実装しています。

DequeTest.pde

interface IntStack {
  void push(int value);
  int pop();
}

interface IntQueue {
  void enqueue(int value);
  int dequeue();
}

interface IntDeque extends IntStack, IntQueue {
  boolean isEmpty();
}

class ArrayDeque implements IntDeque {
  private int[] data = new int[100];
  private int head = 50;
  private int tail = 50;

  public void push(int value) {
    head--;
    data[head] = value;
  }

  public int pop() {
    int value = data[head];
    head++;
    return value;
  }

  public void enqueue(int value) {
    data[tail] = value;
    tail++;
  }

  public int dequeue() {
    int value = data[head];
    head++;
    return value;
  }

  public boolean isEmpty() {
    return head == tail;
  }
}

void setup() {
  IntDeque deque = new ArrayDeque();

  deque.push(10);
  deque.push(20);
  println("スタックから取り出し: " + deque.pop());

  deque.enqueue(30);
  deque.enqueue(40);
  println("キューから取り出し: " + deque.dequeue());
  println("キューから取り出し: " + deque.dequeue());
}

実行結果

スタックから取り出し: 20
キューから取り出し: 10
キューから取り出し: 30

IntDeque インターフェースを実装した ArrayDeque クラスは、push(), pop(), enqueue(), dequeue() の全ての操作を行えるようになっています。

演習

演習1

インターフェースの継承の目的やメリットを踏まえ、プログラムにおいて継承が望ましい具体的な場面を挙げ、その理由を述べなさい。

演習2

「キーを指定して値を読み取る」機能を表す ReadableStore インターフェースと、「キーと値のペアを書き込む」機能を表す WritableStore インターフェースを作成しなさい。 ReadableStore インターフェースには String read(String key) メソッドを、WritableStore インターフェースには void write(String key, String value) メソッドを定義しなさい。

演習3

演習2で作成した2つのインターフェースを多重継承し、「読み書き可能なデータストア」を表す DataStore インターフェースを作成しなさい。 また、DataStore インターフェースには独自のメソッドとして、全てのデータを消去する void clear() を追加しなさい。

演習4

DataStore インターフェースを実装した SimpleCache クラスを作成しなさい。 SimpleCache クラスは内部に String[] keys = new String[10];String[] values = new String[10]; の2つの配列と、現在の保存数を表す int size = 0; フィールドを持ちなさい。 write メソッドで配列の末尾にキーと値を保存し、read メソッドでキーを検索して対応する値を返却するようにしなさい(見つからない場合は "Not Found" を返却すること)。 また、setup 関数内で SimpleCache クラスのインスタンスを生成し、動作を確認しなさい。

演習の解答例

演習1の解答例

場面: ユーザーインターフェース(UI)の様々な部品(ボタン、テキストボックス、スライダーなど)を設計する場面。

理由: 「画面に描画可能」「マウスクリック可能」「キーボード入力可能」といった機能ごとに小さなインターフェースを定義し、それらを多重継承して組み合わせることで、多様な部品を柔軟に作成できるためである。これにより、各インターフェースをシンプルに保ちつつ、メソッドの重複定義を防ぐことができる。

演習2の解答例

解答は以下の通りである。

ReadableStore.java

interface ReadableStore {
  String read(String key);
}

WritableStore.java

interface WritableStore {
  void write(String key, String value);
}

演習3の解答例

カンマ区切りで複数のインターフェースを継承し、独自のメソッドを追加する。

DataStore.java

interface DataStore extends ReadableStore, WritableStore {
  void clear();
}

演習4の解答例

DataStore インターフェースを実装し、全てのメソッドを具体的に定義する。

SimpleCacheTest.pde

class SimpleCache implements DataStore {
  private String[] keys = new String[10];
  private String[] values = new String[10];
  private int size = 0;

  public String read(String key) {
    for (int i = 0; i < size; i++) {
      if (keys[i].equals(key)) {
        return values[i];
      }
    }
    return "Not Found";
  }

  public void write(String key, String value) {
    if (size < 10) {
      keys[size] = key;
      values[size] = value;
      size++;
    }
  }

  public void clear() {
    size = 0;
  }
}

void setup() {
  DataStore cache = new SimpleCache();

  cache.write("name", "Alice");
  cache.write("age", "20");

  println(cache.read("name"));
  println(cache.read("score"));

  cache.clear();
  println(cache.read("name"));
}

results matching ""

    No results matching ""