ロールインターフェース

2006年12月22日

ロールインターフェースは、サプライヤとコンシューマ間の特定のインタラクションに着目することで定義されます。サプライヤコンポーネントは通常、これらのインタラクションパターンごとに1つずつ、複数のロールインターフェースを実装します。これは、サプライヤが単一のインターフェースのみを持つヘッダーインターフェースとは対照的です。

例を使って見てみましょう。PERTスタイルのプロジェクト計画プログラムを考えてみます。このスキームでは、プロジェクトを一連のアクティビティに分割します。次に、これらのアクティビティをネットワーク(厳密には有向非巡回グラフ)に配置して、タスク間の依存関係を示します。そのため、「朝食を食べる」というタスクがある場合、「コーヒーを作る」と「シリアルを混ぜる」は先行アクティビティになる可能性があります。これは、すべての先行タスクが完了するまで、朝食を食べ始めることができないことを意味します。

各アクティビティには、所要時間、つまり、それが完了するまでに予想される時間があります。その所要時間とネットワーク内の関係から、他の情報を把握できます。アクティビティの最早開始時刻は、先行タスクの最も遅い最早完了時刻として計算できます。アクティビティの最早完了時刻は、最早開始時刻に所要時間を加えたものとして計算します。同様に、最遅完了時刻と最遅開始時刻を計算できます。コードは次のようになります。

  private int duration;

  public MfDate earliestStart() {
    MfDate result = MfDate.PAST;
    for (?TYPE? p : predecessors())
      if (p.earliestFinish().after(result))
        result = p.earliestFinish();
    return result;
  }

  public MfDate earliestFinish() {
    return earliestStart().addDays(duration);
  }

   public MfDate latestFinish() {
    MfDate result = MfDate.FUTURE;
    for (?TYPE? s : successors())
      if (s.latestStart().before(result))
        result = s.latestStart();
    return result;
  }

  public MfDate latestStart() {
    return latestFinish().minusDays(duration);
  }

上記のコードには穴があることに気付くでしょう - ?TYPE?です。アクティビティにその先行タスクと後続タスクを問い合わせると、どのようなタイプのオブジェクトが返されるべきでしょうか?(正確にはコレクションが返されることが予想されるため、本当の質問は、返されるコレクションの要素のタイプは何であるべきかということです。)

ヘッダーインターフェースを使用する場合、返されるインターフェースはアクティビティになり、アクティビティクラスのパブリックメソッドをミラーリングしてインターフェース実装ペアを作成します。

public interface Activity ...
  MfDate earliestStart();
  MfDate earliestFinish();
  MfDate latestFinish();
  MfDate latestStart();

class ActivityImpl...
  List<Activity> predecessors() ...
  List<Activity> successors() ...

しかし、ロールインターフェースでは、共同作業オブジェクトが実際にどのように使用されているかを確認します。この場合、後続タスクはlatestStart(最遅開始時刻)にのみ使用され、先行タスクはearliestFinish(最早完了時刻)にのみ使用されます。そのため、実際に使用するメソッドのみを持つ2つのインターフェースを作成します。

public interface Successor {
  MfDate latestStart();
}
public interface Predecessor {
  MfDate earliestFinish();
}

class Activity
  List<Predecessor> predecessors() ...
  List<Successor> successors() ...

後続タスクは、共同作業オブジェクトがこのオブジェクトに対して果たす役割と考えることができます。オブジェクトと、他のオブジェクトと共同作業する際にオブジェクトが果たす役割について考えるこのアプローチは、オブジェクト指向の世界では長い歴史があります。

ロールインターフェースの強みは、アクティビティとその後続タスク間の実際の共同作業を明確に伝達することです。多くの場合、クラスはクラスのすべてのメソッドを使用するわけではないため、実際に必要なメソッドを示すことは良いことです。これは、後で置き換える必要がある場合に特に役立ちます。ヘッダーインターフェースは、必要のないメソッドでもすべて実装することを強制しますが、ロールインターフェースでは必要なものだけを実装すれば済みます。

ロールインターフェースの欠点は、ロールインターフェースを形成するために各共同作業を検討する必要があるため、作成に手間がかかることです。ヘッダーインターフェースでは、パブリックメソッドを複製するだけで、考える必要はありません。また、コンシューマへの依存という感覚もあります。正式な依存関係はないため「感覚」と言っていますが、それでも多くの人々を不安にさせるには十分です。彼らは、誰がどのようにサービスを使用するかは気にするべきではないと考えているため、ヘッダーインターフェースを好みます。インターフェースを公開すれば、役に立つと思えば使用できます。

全体として、私はロールインターフェースの方がはるかに好きなので、できるだけロールインターフェースにプッシュすることをお勧めします。これには作業が必要ですが、私の信念は、置き換え可能性が本当に必要な場合にのみインターフェースを使用するべきであり、インターフェースが必要な場合は、そのインターフェースのコンシューマが必要とするものについてよく考えるべきだということです。

Webサービスのようなものを使用してリモートコンテキストでこれを考えると、興味深いひねりがあります。リモートサービスに先行タスクの詳細を問い合わせる場合、何が返されるべきでしょうか?ロールインターフェースであるためには、最早完了時刻データのみを含むドキュメントを返す必要があると主張する人もいるかもしれません。私は同意しません。要求した以上のデータを含むドキュメントを返すのは perfectly valid(完全に有効)だと思います.重要なのは、関連する型チェックは、最早完了時刻データが存在することを確認することだけであるということです。追加データを無視できる場合、それを提供することは犯罪ではありません。クラスが複数のインターフェースを実装できるのと同じです。この考え方は、コンシューマ主導契約の哲学と一致しており、これが私がコンシューマ主導契約を非常に魅力的だと感じる理由の1つです。

私が示したように、この概念は長い間存在していました。トリグヴェ・リーンスカウグは、役割を分析し、それらをクラスに統合することに基づいた方法論の本を書きました。ロバート・マーチンは、このトピックについてインターフェース分離の原則として語っています。ロールインターフェースはこの原則に従いますが、ヘッダーインターフェースは従いません。