仕様(インターフェイス)と実装の詳細 (1)

APIの公開/非公開が意識できるようになると共に、「仕様(インターフェイス)」と「実装の詳細」を意識できるようになるとよい。

  • このクラスの公開APIはどれか、非公開APIはどれか
  • このクラスの仕様(インターフェイス)はどれか、実装の詳細はどれか

という2つの角度でクラスをとらえる。前者については昨日のエントリでも示した通り、可視性がpublic,protectedなものが公開APIであり、その他は非公開APIである。機械的に判断できますね。では後者についてはどう判断すればいいか?

注:下の例で「このクラスのインターフェイスは?」と聞かれると単純に「Baz」と答えるかもしれない。がここで言いたいのは「Bazを介したFooのインターフェイス」ではなく「Foo自身が純粋に外部に公開する仕様としてのインターフェイス」のこと。インターフェイスとは何か?についてはinterfaceについて本気出して考えてみた - 都元ダイスケ IT-PRESS参照。この「操作パネル」のことです。対する実装の詳細とは、この「内部の電子回路」のことです。

/**
 * ...
 */
public class Foo extends Bar implements Baz {

  /**
   * ...
   */
  public static final String CONST = "xxx";

  /**
   * ...
   */
  String hoge;

  /**
   * ...
   */
  private String piyo;
  
  /**
   * ...
   */
  public Foo(String hoge, String piyo) {
    this.hoge = hoge;
  }
  
  /**
   * ...
   */
  public String getHoge() {
    return hoge;
  }
  
  /**
   * ...
   */
  void setHoge(String hoge) {
    this.hoge = hoge;
  }
  
  /**
   * ...
   */
  protected String getPiyo() {
    return piyo;
  }
  
  /**
   * ...
   */
  private setPiyo(String piyo) {
    this.piyo = piyo;
  }
  
  // ...いろいろ
}

さて、上記のコードの「仕様」と「実装の詳細」はどの部分だと言えるだろうか。簡単な判断方法としては、このFooをインターフェイスに変えてみると分かる。実際には、FooインターフェイスはBarクラスをextendsできないけども。Barもクラスじゃなくてインターフェイス化したと考え、ともかくインターフェイスにしてみる。

フィールドは、public static final なもの以外はエラーが出てしまうので削除。メソッドもpublicなもの以外はエラーが出てしまうので削除。メソッドシグネチャの後の { ... } はあるとエラーになってしまうので削除。という手順だ。残ったのはこれ。

/**
 * ...
 */
public interface Foo extends Bar, Baz {

  /**
   * ...
   */
  public static final String CONST = "xxx";

  /**
   * ...
   */
  String getHoge();
  
  
  // ...いろいろの一部
}

これが、このFooクラスの「仕様(インターフェイス)」だ。消した部分が「実装の詳細」と言われる部分である。ざっくりとした判断方法なので、protectedの扱いが微妙に間違っているのだが。protectedのシグネチャも仕様の一部である…*1。まぁまとめると、

  • classをinterfaceに変えて残った部分+protectedなメソッドのシグネチャ部 → 仕様(インターフェイス
  • classをinterfaceに変えて消した部分(publicメソッドのボディ { ... } も含む!) → 実装の詳細

である。ちなみに、「仕様(インターフェイス)」と「公開API」はおおよそ一致する。従って、2次元的に分析すると、クラス定義の各要素は以下の3種類に分類できるんじゃないかな。

  • 公開API かつ 仕様(インターフェイス) → 前述
  • 非公開API かつ 実装の詳細 → interface化して消えた「シグネチャ」(protectedを除く)
  • 単なる実装の詳細 → interface化して消えた「メソッドボディ」( {...} の部分)

以上がはっきり分かれて見えるようになったら、Fooクラスを使う(Fooクラスに依存する)クラスXを書く場合、そのXはFooの「仕様」に依存すべきであり、「実装の詳細」に依存すべきではないという指針に従う。

これも簡単に言い直すと、「Xを書く際に、実装の詳細を眺めながら書いてはならない。仕様(シグネチャJavadoc)だけを頼りにXを書くべし」。Xを書く時に、Fooのメソッドボディを見てはならない*2

という立場になって考えると、自ずと「FooのJavadocに書くべき事柄」が決まってくるはずだ。こんな情報を与えてやらないと、実装の詳細を見なければXを書けないよな…、と。

昔から高凝集低結合というスローガンがあるが、誤解を恐れずにに言えば「モジュールを超えて実装を追う必要がない状態」が低結合だ。Javadocがないだけで、結合度を高めてしまうことになる。気をつけよう。

http://d.hatena.ne.jp/daisuke-m/20091014/1255506192

手前味噌だが、FooにJavadocが無かった場合、Xの実装者はFooの実装の詳細を見てXを書く。つまり実装の詳細に依存しはじめ、FooとXの結合度が高まってしまう。

まとめ

  • 「仕様(インターフェイス)」と「実装の詳細」を見分けよう。
  • 外部から「実装の詳細」に依存しないようにしよう。
  • 外部から「実装の詳細」に依存させないために必要な説明をJavadocに記述しよう。

*1:つまり、interfaceにprotectedメソッドを定義できても良い気はするのだ。しかし言語仕様的にそれを禁じているのは、複雑になりすぎるからなのかな…。

*2:厳しすぎる指針だが、意気込みとして。