必要なインターフェース

2015年10月12日

必要なインターフェースとは、インタラクションのクライアントによって定義されるインターフェースです。インタラクションで利用するためにサプライヤコンポーネントに必要なことを指定します。

必要なインターフェースの良いサンプルは、一般的に「comparable」と呼ばれるインターフェースです。このインターフェースは、通常は並べ替え関数によって必要とされます。たとえば、アルバムのセットがあり、記事(「The」、「A」、「An」など)を無視してタイトルで並べ替えるとします。「comparable」の必要なインターフェースを実装することで、そのように並べ替えるようにする手続きを準備できます。

Javaでは、次のようになります。

class Album...

  public class Album implements Comparable<Album> {
    private String title;
  
    public Album(String title) {
      this.title = title;
    }
    public String getTitle() {
      return title;
    }
  
    @Override
    public int compareTo(Album o) {
      return this.sortKey().compareTo(o.sortKey());
    }
    private String sortKey() {
      return ignoreSortPrefixes(title).toLowerCase();
    }
    private static String ignoreSortPrefixes(String arg) {
      final String[] prefixes = {"an", "a", "the"};
      return Arrays.stream(prefixes)
              .map(s -> s + " ")
              .filter(s -> arg.toLowerCase().startsWith(s))
              .findFirst()
              .map(s -> arg.substring(s.length(), arg.length()))
              .orElse(arg)
              ;
    }

この場合、Comparableは、さまざまなJava並べ替え関数の必要なインターフェースになります。より複雑な例では、いくつかメソッドで定義した豊かなインターフェースを持つことができます。

インターフェースは、クライアントに公開するものについてサプライヤが行う決定として考えることが多いです。しかし、必要なインターフェースはクライアントによって指定され(定義もよく行われます)。クライアントに求められるものを考えることで、より便利なインターフェースを得ることがよくあります。そうなると、RoleInterfacesについて考える方向に進みます。

アダプターの使用

独立して定義された2つのモジュールを接続したい場合に、共通の問題が発生します。この場合、名前が一致していても困難に直面する可能性があります。

タスクの必要なインターフェースを持つタスクリストを考えてみましょう。

class TaskList...

  private List<Task> tasks;
  private LocalDate deadline;
  public LocalDate latestStart() {
    return deadline.minusDays(tasks.stream().mapToInt(t -> t.shortestLength()).sum());
  }
}

interface Task…

  int shortestLength();

別のサプライヤから取得したActivityクラスと統合してみましょう。

class Activity…

  public int shortestLength() {
    …

アクティビティのメソッドは必要なインターフェースのシグネチャと一致しますが、型定義が一致しないため、(正当な理由で)タスクリストのアクティビティを作成することはできません。アクティビティクラスを変更できない場合は、アダプターを使用する必要があります。

public class ActivityAdapter implements Task {
  private Activity activity;

  public ActivityAdapter(Activity activity) {
    this.activity = activity;
  }
  @Override
  public int shortestLength() {
    return activity.shortestLength();
  }
}

ソフトウェアの世界では、アダプターという用語をかなり自由に使用していますが、ここではギャング・オブ・フォーの書籍での意味で厳密に使用します。この用語では、アダプターは、あるオブジェクトを別のオブジェクトの必要なインターフェースにマッピングするオブジェクトです。

この場合、動的言語を使用している場合はアダプターは必要ありませんが、アクティビティクラスが異なるシグネチャを持つメソッドを使用している場合は必要です。

謝辞

Alexander ZagniotovとBruno Trecentiがこの投稿の草案にコメントしました。