Powered by SmartDoc

Essential Java Styleのエッセンス(^^;

Sat Jun 22 02:26:04 JST 2002
TAKAHASHI Toru
torutk@alles.or.jp
http://www.alles.or.jp/~torutk/oojava/
書籍"Essential Java style : patterns for implementation"を読んでのメモと感想などをまとめたものです。この本は、Javaのコードを記述する際の問題と解法をパターンっぽく整理したものです。この本を書くにあたって著者は、Smalltalk Best Practice Patternsを下敷きにしています。

目次

振舞い - メソッド

「長いメソッドを作るのではなく、短い沢山の小さなメソッドを作ろう」というのがこの章の趣旨です。長いメソッドは、コードの重複が増えるし、再利用しづらいし、クラスが解読しづらく保守しづらいからです。また、パフォーマンスのボトルネックを調べるのも困難です。なぜなら、多くのプロファイラはメソッド単位に実行時間を計測するからです。

Composed Method

解決すべき問 クラスをメソッドにどう分割するか
解決 小さなメソッドを作成し、それぞれメソッド名が表現する単一のタスクを果たす
分類 振舞い
関連 Method Comment、Intention-Revealing Method Name

このパターンがEssential Java Style中で最重点パターンだそうです。Composed Methodを使い、保守しやすい良いクラスを構築しましょう。

クラスを開発する時に念頭に置くことは、「まず動かす、次に正しくする、最後に速くする」("make it run, make it right, make it fast")です。この書で紹介しているパターンはいずれも「正しくする("make it right")」ためのパターンです。開発はこの3つの段階に沿って進めます。

動かす(make it run)

動かすことはどんなソフトウェア開発でも最も重要な目標。初期のクラス開発では、

  1. 公開メソッドを決めて命名する。クラスは振舞いを利用するオブジェクトへ提供するので、これが分からないということは設計が誤っていることになる。
  2. クラスのインスタンスが持つ属性をインスタンス変数として定義する。
  3. 公開メソッドをコーディングする。この時点でいくつか補助的なメソッドが加わるだろう。もっとも手早くメソッドを仕上げるにはトップダウンでコーディングするため。

この3.の段階で出来たメソッドはちゃんと動くけど、簡単には保守したり拡張することができないでしょう。たいてい長いメソッドになっています。それは悪いこと?コードは読みやすいですか、空白・インデントは一貫していますか、段落分けはありますか、長すぎませんか、一つのメソッド内で多くさんのタスクを行っていませんか。継承、再利用が困難で、コードのあちこちに重複した部分が散在してしまいます。

小さなメソッドで構築すると、読みやすく、置き換えやすく、拡張しやすく、テストしやすいクラスになります。

正しくする(make it right)

長いけれど動くメソッドが出来上がったら、次は正しくする番です。正しくするには、この本にあるパターンを適用して、クラスを出来るだけ読みやすく保守しやすくします。その第一歩がComposed Methodです。

適用前
public void createCustomerCSV() throws IOException {
  String tempFilename = null;
  for (int i=0; i<1000; i++) {
    String filename = "temp." + i;
    File file = new File(filename);
    if (!file.exists()) {
      tempFilename = filename;
      break;
    }
    if (tempFilename == null) {
      throw new IOException(
        "Could not create temp file");
    }
  }
  FileWriter output = new FileWriter(tempFilename);
  try {
    Iterator iterator = customers.iterator();
    while (iterator.hasNext()) {
      Customer customer = (Customer)iterator.next();
      output.write(customer.getName() + ",");
      output.write(customer.getCity() + ",");
      output.write(customer.getCountry() + "\n");
    }
  } finally {
    output.close();
  }
}

このメソッドは3つの異なるタスクを実行しています(実際のコードは略)。

適用後
public void createCustomerCSV(String filename) throws IOException {
  FileWriter output = new FileWriter(filename);
  try {
    Iterator iterator = customers.iterator();
    while (iterator.hasNext()) {
      writeCustomer(output, (Customer)iterator.next());
    }
  } finally {
    output.close();
  }
}

public String getTemporaryFilename(String prefix) throws IOException {
  for (int i=0; i<1000; i++) {
    String filename = prefix + "." + i;
    File file = new File(filename);
    if (!file.exists()) {
      return filename;
    }
  }
  throw new IOException(
      "Could not create temp file");
}

private void writeCustomer(FileWriter output,
                           Customer customer) throws IOException {
  output.write(customer.getName() + ",");
  output.write(customer.getCity() + ",");
  output.write(customer.getCountry() + "\n");
}

メソッド名が何をしているかを示しているので理解しやすくなりました。見てすぐにそのメソッドが何をしているか理解できないなら、そのメソッドは多くのことをし過ぎなのです。

出力ファイル名を柔軟に選べます。例えばテンポラリファイル名のロジックだけ入れ換えることができます。

出力フォーマット変更は、writeCustomerメソッドを修正するだけです。

速くする(make it fast)

願わくはこの項目が必要にならないことを。でもいずれは誰かが遅い!と不平を上げるでしょう。結局Javaなのだから。

性能の問題にぶち当たった時に犯してはならない誤りは、どこに問題があるかを仮定することです。大抵それは間違っています。仮定せずに、ツールを使って測定しましょう。「正しくされ」たプログラムなら、すぐに性能のボトルネックがどこか検出できます。長いメソッドばかりのプログラムでは、どこがボトルネックか検出することが困難です。

性能を向上するための修正はコードを理解しずらくするので、なぜそうなのかをコメントを加えます。保守性が悪くなるので、性能向上の変更は最後の最後にします。

Constructor Method

解決すべき問 インスタンス生成をどのように表すか
解決 インスタンスを生成する有効なコンストラクタを提供し、無効なオブジェクトを生成するコンストラクタは提供しない
分類 振舞い
関連 なし

コンストラクタを作成しない場合、Javaでは引数なしのデフォルトコンストラクタが提供されます。明示的にコンストラクタを作成すると、引数なしのコンストラクタは利用できなくなります。

クラスのインスタンス生成時にインスタンスが有効な状態になっているべきです。もし引数なしのコンストラクタが有効なインスタンスを生成できない場合、有効なインスタンスを生成するのに必要な情報をコンストラクタの引数で渡せるコンストラクタを提供します。

適用前
Manager manager = new Manager();
manager.setName("Blow, Joseph");
manager.setSSN("999-13-1349");
manager.setContractedRate(40);
適用後
public Manager(String name,
               String ssn,
	        int contractedRate)
public Manager(String name,
               String ssn,
	        int contractedRate,
		 boolean hasGoldenParachute)

これは割と自然な考え方です。ただ、JavaBeans規約に従うクラスを作成する場合は、必ず引数なしのコンストラクタが必要になるので、このパターンが適用できないです。

Constructor Parameter Method

解決すべき問 Constructor Methodの引数をどのようにインスタンス変数に設定するか
解決 Direct Value Accessを適用する。複数のコンストラクタが共通にパラメータを設定する必要があるときは、private setメソッドを作成する。
分類 振舞い
関連 Indirect Variable Access、Direct Variable Access、Default Parameter Values

もしIndirect Variable Accessを適用していると、コンストラクタ中でsetterメソッドを使用することになります。オブジェクト生成時と生成後とでは、属性へ設定する条件が異なることもあるので、コンストラクタ中ではsetterメソッドを避け、Direct Variable Accessを使う。

複数のコンストラクタがあったり属性の設定に複雑な操作が必要なときは、可視性をprivateとするConstructor Parameter Methodとしてsetという名で提供する。

Construtor Parameter Methodの例
public Manager(String name, String ssn, int contractedRate) {
  set(name, ssn, contractedRate);
}
private void set(String _name, String _ssn, int _contractedRate) {
  name = _name;
  ssn = _ssn;
  contractedRate = _contractedRate;
  baseSalary = contractedRate * 2000;
}

引数の変数名の最初に_を付けているのは、この本の命名規則です。インスタンス変数にDirect Variable Accessで値を設定するときに使います。

Default Parameter Values

解決すべき問 引数にデフォルト値をどうやって設定するか
解決 必要な引数の組み合わせだけオーバーロードしたメソッドを作成し、引数の少ないメソッドからより多い引数のメソッドへ委譲する。
分類 振舞い
関連 Constructor Parameter Method
int setCursorTo(int x, int y) {
    :
}
int setCursorTo(int x) {
  return setCursorTo(x, 1);
}
int setCursorTo() {
  return setCursor(1, 1);
}

引数のうち、省略可能なものと必須なものとを区分けする。最初に、全ての引数を持つメソッドを記述する。その時必須引数を左側に置き、続いて省略可能な引数を並べる。次に、省略可能な引数を一つずつ減らしたメソッドを記述していく。Javadoc表示上、ソースリスト上は引数の少ないものを先頭にする。

Shortcut Constructor Method

解決すべき問 オブジェクトの生成を簡潔にするには
解決 引数を初期値として新しいオブジェクトを生成するメソッドを提供する
分類 振舞い
関連 Converter Method

文章クラス(Sentence)があったとし、これに単語(Word)を結合する状況を考えます。

new Sentence(existingSentence, word);

普通はこのようにコーディングするでしょう。

Shortcut Constructor Method適用
public Sentence cat(Word word) {
  return new Sentence(this, word);
}

とメソッドを提供すると、

existingSentence.cat(word);

と書くことができます。

Converter Method

解決すべき問 あるオブジェクトを別なオブジェクトへ同じプロトコルで変換するには
解決 可能ならConverter Constructor Methodを使え、そうでない時はasTargetClass()メソッドを作り、targetクラスのインスタンスを生成しreturnする
分類 振舞い
関連 Converter Constructor Method、Shortcut Constructor Method

クラスAからクラスBへ変換したいとき、クラスAに変換メソッドを追加してしまいがちです。でも、クラスAからクラスBへの依存性が発生してしまいます。

GruntクラスからManagerクラスへ変換する場合、

避けるべき例
public asManager() {
  initialContractedRate = Math.round(getSalary() / 2000);
  return new Manager(name, ssn, initialContractedRate, false);
}

とGruntクラスに書いてしまいがちですが、GruntクラスがManagerクラスへ依存してしまいます。Managerクラスのコンストラクタが変更されたらGruntクラスも変更しなければならないし、新しいクラスContractorを追加したら、asContractorメソッドをあちこちに追加してまわるはめになってしまいます。

ここで、もし変換先クラスを変更することが可能なら、Converter Constructor Methodパターンを使ってください。それが出来ないときだけ、Converter Methodを使います。

Converter Method例
public asManager() {
  return new Manager(this);
}

Converter Constructor Method

解決すべき問 あるオブジェクトを別なオブジェクトへ異なるプロトコルで変換するには
解決 変換元オブジェクトを引数にとるコンストラクタを変換先クラスに設ける
分類 振舞い
関連 Converter Method

コンスラクタを下記のように作ります。

public TargetClass(SourceClass sourceParam)

Query Method

解決すべき問 オブジェクトの属性を評価するには
解決 問い合わせのような名前のメソッドを提供する。評価する属性の接頭辞にbe動詞かその派生を置く(isOpen、hasDependents、wasDeletedなど)。戻り値はbooleanとする
分類 振舞い
関連 Enumerated Constants

たいていの属性は、真偽値(boolean)や少ない範囲の列挙値で定義されます。オブジェクトの型によらず同じように評価したい場合に使います。

真偽値(boolean)属性の問い合わせ
public boolean wasHiredByeCEO() {
  return wasHiredByeCEO;
}
列挙属性の問い合わせ
public boolean isFullTime() {
  return status == FULL_TIME;
}
public boolean isPartTime() {
  return status == PART_TIME;
}

クラスを利用する側のコードは、

クラスを利用する例
is (employ.isFullTime()) { ...

と書けるので、読みやすくなります。

列挙の種類が2、3個をはるかに超えるときや今後増えることが予想されている場合は、Enumerated Constantsパターンを使います。

Comparing Method

解決すべき問 関連するオブジェクトをどうやって順序つけるか
解決 Comparableインタフェースを実装し、Collections.sort(List)を呼ぶ。特殊な順序に並べるときは、Collections.sort(List、 Comarator)を呼ぶ。
分類 振舞い
関連 Equality Method

Java 2から導入されたコレクションAPIですね。

Reversing Method

解決すべき問 メッセージの流れをどのようにスムーズにするか
解決 引数のクラスに新しいメソッドを追加する。新たなメソッドは元の受取手を引数に取り、元の受け取り手にthisを引数としたメッセージを送り返す。
分類 振舞い
関連 なし

例えば以下のコードは非常にきれいで見て分かりやすいです。

見やすい一連の呼び出し
list.add("string 1");
list.add("second string");
list.add("3rd string");
list.remove("second string");

開発者の多くは見た目の理解しやすさが持たらす価値を割引いて考えがちですが、コードを読みやすくする点で非常に効果的なのです。

Reversing Methodは見た目を高めるパターンですが、コードの振舞いを変更するのでこの章に含まれています。

見にくい一連の呼び出し
canvas.moveTo(3, 4);
canvas.drawLineTo(7, 11);
bulletArt1.drawOn(canvas);
canvas.drawLineTo(12, 13);
bulletArt2.drawOn(canvas);

ここで、canvasがメッセージの受け取り手となっているコードの中に、別なオブジェクトがメッセージの受け取り手となる個所があります。

Reversing Methodを適用し、canvasオブジェクトのクラスにdrawメソッドを実装し、メッセージの受け取り手を逆にします。

canvasオブジェクトのクラスに追加したdrawメソッド
  public void draw(Art art) {
    art.drawOn(this);
  }
Reversing Method適用後
canvas.moveTo(3, 4);
canvas.drawLineTo(7, 11);
canvas.draw(bulletArt1);
canvas.drawLineTo(12, 13);
canvas.draw(bulletArt2);

Method Object

解決すべき問 たくさんのコードがたくさんの引数や一時変数を共有するメソッドをどうやってコーディングするか
解決 メソッドの直後にインナークラスを定義し、元のメソッド中の一時変数をインスタンス変数として定義し、一時変数を一つのコンストラクタの引数で渡す。computeメソッドを定義し、元のメソッドの処理を実行する。Method Object中にComposed Methodを適用する。
分類 振舞い
関連 Composed Method、Parameter Object、Collecting Temporary Variable

長くてみにくいメソッドを整理するためのパターンです。Composed Methodを適用して小さなメソッドに分解した結果、たくさんの一時変数をメソッド間で受渡ししなくてはならないことがあります。これらをインスタンス変数にしてしまうのは避けたい所です。そんな時には、普通Parameter Objectパターンを使いますが、それでも複雑な場合にはこのMethod Objectパターンを適用します。

長くみにくいメソッド
void longComplex() {
    : 
}

何十行もある(もしかしたら何百行!)メソッドlongComplexがあったとします。まず、Composed Methodパターンを適用して、小さなメソッドに分解した場合を考えます。

Composed Method適用後
void longComplex() {
  simpleA(a, b, c, ...);
  simpleB(a, b, c, ...);
  simpleC(a, b, c, ...);
    :
}

単一のタスクを処理するメソッド群に分解されますが、一時変数を引数や戻り値としてメソッド間で受渡しています。この結果、コードの読みやすさがあまり変わらなかったり保守性がそれほど向上しなかったりします。

そこで、長くてみにくいメソッドをインナークラスのメソッドとして定義します。

Method Object適用後
void longComplex() {
  Calculator cal = new Calculator(a, b, ...);
  cal.compute();
}
  :
class Calculator {
    :
  void compute() {
    a();
    b();
    c();
      :
  }
    :
}

インナークラスCalculatorに、長くてみにくいメソッドを移行します。インナークラスでは、Composed Methodを適用して小さなメソッド群に分解します。次に、小さなメソッド間で受渡ししていた引数群をインナークラスのインスタンス変数に定義します。これで、すっきりしたコードになります。

Parameter Object

解決すべき問 たくさんのコードがたくさんの引数や一時変数を共有するメソッドをどうやってコーディングするか
解決 Composed Methodを適用する。メソッド間で受渡しする引数群を保持するインナークラスを定義する。分解されたメソッドの中からParameter Objectの保持するデータへ直接アクセスする
分類 振舞い
関連 Method Object、Composed Method

長くてみにくいメソッドを整理するためのパターンです。Composed Methodを適用して小さなメソッドに分解した結果、たくさんの一時変数をメソッド間で受渡ししなくてはならないことがあります。Cの構造体のように、これらをインスタンス変数として保持するインナークラスを定義します。各メソッドでは、このインスタンス変数を直接アクセスします。

小さなメソッドに分解
void longComplex() {
  mA(a, b, c, ...);
  mB(a, b, c, ...);
  mC(a, b, c, ...);
    :
}
ParameterObject適用後
void longComplex() {
  Params param = new Params();
  mA(p);
  mB(p);
  mC(p);
    :
}

class Params {
  T a;
  T b;
  T c;
   :
}

Debug Printing Method

解決すべき問 デバッグのためにオブジェクトの印字可能な表現をどうやって提供するか
解決 toString()メソッドをオーバーライドしオブジェクトをユニークに記述する簡潔な文字列を返却する
分類 振舞い
関連 なし

Objectクラスで定義されるtoString()メソッドは、オブジェクトのハッシュコードを16進表現文字列で返却します(例:Employee@fc825d21)。これはあまり使える内容ではないので、作成するクラスではtoString()メソッドをオーバーライドするべきです。ただし、toString()メソッドをユーザインタフェース用に使うことは勧められません。

簡単なtoStringメソッド
public String toString() {
  return getName() + " (" + getSSN() + ")";
}

コレクションに格納されるオブジェクトをデバッグするときは、クラス名も印字されると便利です。

クラス名を印字するtoStringメソッド
public String toString() {
  return getClass().getName() + " [" +
         getName() + " (" + getSSN() + ")]";
}

規約として定めるのはよいことでしょう。規約をサポートするように、クラス名を印字する部分を括り出してもよいでしょう。

クラス名印字を共通化
public String classTaggedString(Object object, String string) {
  return object.getClass().getName() + " [" + string + "]";
}

このメソッドはどこに入れればいいでしょうか?よく使われるのが、デバッグ関係のユーティリティを収めるDebugクラスです。

一貫した表現を行うtoStringメソッド
public String toString() {
  return Debug.classTaggedString(
    this, getName() + " (" + getSSN() + ")");
}

Method Comment

解決すべき問 メソッドにコメントをどのようにつけるか
解決 必要な場合にだけ、メソッドの最初にJavadocコメントとは別に開発者用コメントを提供する。コードでは明白でない重要な情報を伝えるだけにコメントする。不明瞭なコードはComposed Methodといった他のパターンを使ってリファクタする
分類 振舞い
関連 Composed Method

コメントとコードとの間の正確性を保つ術はありません。不正確なコメントがある位ならば、コメントが無い方がずっとましです。Javadocはクラスの利用者へ、そのクラスの理解を助ける情報は提供しますが、保守に必要な情報までは提供しません。コメントするべき項目は以下のとおりです。

不明瞭なコードにコメントするより、コードをきれいに書き直します。

不明瞭なコード
if (ftpResponse.charAt(0) == '2') {
  // ftpの応答が200なら成功

これを、次のようにメソッドとして書き直します。

コードを明瞭にするためにメソッド化
public boolean wasSuccessful(String ftpResponse) {
  return ftpResponse.charAt(0) == '2';
}

すると、以下のようにコメントをつけなくても明確なコードになります。

明瞭なコード
if (wasSuccessful(ftpResponse)) {

Intention-Revealing Method Name

解決すべき問 メソッドをどんな名前にするべきか
解決 メソッドが何をしているかを名前にする。どのようにしているかではない
分類 振舞い
関連 Composed Method、Getting Method、Setting Method、Query Method

メソッドの命名は、クラス設計における最重要事項です。単一のタスクしか実行しない小さなメソッドに分割されていれば、正確に名前を付けられます。一般的には、メソッドが完遂する動作を表す動詞です。例えば、transmit()、encode()など。

メソッドの分類

略語は使わないようにします。コードを入力するのは1回ですが、コードが読まれるのは何百回かもしれません。入力の手間を削減することに価値はありません。読みやすくすることに価値があるのです。

不明瞭コード
if (employee.getTerminationDate() != null)

このコードは従業員が解雇されたかどうかを判定しています。これは読みにくいです。否定論理だし、解雇されたことを判定しているのか分かりかねます。きっとコメントを付けたくなるでしょう。しかし、それはComposed Methodを適用する状況の現れです。

また、従業員が雇用されているならば解雇日がnullであるという、ある特定の実装に依存しています。もしEmployeeクラスが変更され、9999年なら雇用されていることになったらどうでしょう?

Intention-Revealing Method Nameの適用
public boolean isTerminated() {
  return getTerminationDate() != null;
}

これはQuery Methodです。

振舞い-メッセージ

オブジェクトは自分自身や他のオブジェクトとコミュニケーションしています。自分自身とのコミュニケーションとは、そのオブジェクト自身が持つ振舞いを起動することです。前章のパターン、特にComposed Methodを適用すると、オブジェクト内部のコミュニケーションが増えます。オブジェクトがコミュニケーションする仕組みは、「メッセージ」です。オブジェクトに何かさせるためにメッセージを送り、メッセージを受け取ったオブジェクトは適合するシグニチャを持つメソッドを起動します。前章のメソッドパターンは、特定の実装やクラスのコードを組織化するかについて扱いましたが、メッセージパターンではメソッドが互いにコミュニケートする方法を扱います。

Message

解決すべき問 処理をどのように起動するか
解決 オブジェクトやクラスに関数呼び出しの形式でメッセージを送り、受け手にどのメソッドを起動するか決定させる
分類 振舞い
関連 Choosing Message、Decomposing Message

いわゆる関数のスコープは、大域かモジュール内かのどちらかです。オブジェクト指向では、クラスというスコープを持ちます。JavaやSmalltalkでは全てのメソッドがクラスに組み込まれます。ある機能を実行するには、メッセージをオブジェクトに送らねばなりません。ポリモルフィズムによって、メッセージを送った結果として実行されるサブルーチンがどれか動的に決定できます。

Choosing Message

解決すべき問 複数の候補からどうやって1つを選んで実行するのか
解決 オブジェクトにメッセージを送り、そのオブジェクトのクラスに振舞いを決めさせる(ポリモルフィズム)
分類 振舞い
関連 なし

どの言語にも、つい乱用してしまう仕掛けが提供されています。Javaの場合、instanceofがその1つです。instanceofにも正しい使い方があります。それは例えばequalsメソッドの実装で引数が同じクラスであることを比較するときです。でも、instanceofを使う多くの場合は、オブジェクトを作り直した方がよいことを示しています。

double annualCompensation = 0;
if (employee instanceof Contractor) {
  annualCompensation =
    (Contractor)employee.getHourlyRate * 2000;
} else if (employee instanceof Grunt) {
  annualCompensation =
    (Grunt)employee.getMonthlySalary() * 12;
} else if (employee instanceof Manager) {
  annualCompensation =
    (Manager)employee.getContractRate() *
    (Manager)employee.getContractPeriod();
}

ここでのinstanceofの使用は、switch文と同じです。こうしたコードの欠点は以下のとおりです。

Choosing Messageパターンは、オブジェクト指向3大格言の1つポリモルフィズム「1つのインタフェース、複数のメソッド」を亨受します。

Choosing Message適用
// class Contractor
  public double getAnnualCompensation() {
    return getHourlyRate() * 2000;
  }

// class Grunt
  public double getAnnualCompensation() {
    return getMonthlySalary() * 12;
  }

// class Manager
  public double getAnnualCompensation() {
    return getContractRate() * getContractPeriod();
}

// 利用するコード
  employee.getAnnualCompensation();

もはや、キャストは不要になり、if文の山も消え、instanceofも使う必要がありません。コメントするまでもない1行のメソッドになっています。

もし、Employeeオブジェクトにcompensation計算を組み込みたくなければ、デザインパターンで述べられているVisitorパターンを使うとよいでしょう。

Decomposing Message

解決すべき問 処理の一部をどうやって起動するか
解決 自身(this)に複数のメッセージを送る
分類 振舞い
関連 Composed Method

古き良き機能分割は不滅です。良いOO設計の結果、サブシステム、オブジェクト、公開メソッドが得られます。Composed Methodでオブジェクトを単一のタスクだけを実行する小さな塊に分解します。しかし、制御とかドライバ的なメソッドはそれ以上分解できません(複雑なイベントの手順を扱っているため)。サブタスクを実行する一連のメソッドを呼んでいきます。

Decomposing Messageの例
private void calculatePay(Employee employee, int payPeriod) {
  calculateBiWeeklyPayAmount();
  calculateFICATax();
  calculateLocalTax();
  calculateStateTax();
  addOutOfStateAdjustments();
  calculateFederalIncomeTax();
  calculateMedicareTax();
  subtractPreTaxDeductions();
  subtractPostTaxDeductions();
}

Dispatch Interpretation

解決すべき問 2つのオブジェクトが、片方の表現を隠蔽してどのように協調するか
解決 責務を持つオブジェクトの中に表現を隠蔽する。クライアントオブジェクトが実装しなければならないインタフェースを生成する。クライアントオブジェクトは、責務を持つオブジェクトからインタフェースの1つのメソッドを通して呼ばれる
分類 振舞い
関連 Mediating Protocol

instanceof演算子が頻繁に使われているときと同じ様に、switch文が使われるときはたいていコードを再構成するべき現象です。あるクライアントオブジェクトが、利用するオブジェクトの属性に対してswitch文を記述しなければならないとします。他のクライアントオブジェクトも同じ様にswitch文を記述しなければならなくなります。これはコードの重複を意味し、避けるべきコーディングです。

属性に対する振り分けを使う例
public class BillingSystem {
  public void bill() {
    OrderType type = order.getType();
    if (type.equals(OrderType.NEW)) {
      // 新規注文の処理
    } else if (type.equals(OrderType.CANCEL)) {
      // 注文のキャンセル処理
    } else if (type.equals(OrderType.ADD_SERVICE)) {
      // サービス追加の処理
    } else if (type.equals(OrderType.REMOVE_SREVICE)) {
      // サービス削除の処理
    } else if (type.equals(OrderType.MODIFY_SERVICE)) {
      // サービス変更の処理
    }
  }
}

ここで、if文のネストはswitch文と同様です。

Orderクラスの属性typeによって処理を振り分けています。Orderを利用する他のクラスでも同様の処理を行う必要があります。コードの冗長が増すので品質劣化が生じます。Orderクラスに変更があると、全てのクラスを変更しなくてはならないので、システムに欠陥が生じるリスクがあります。

振り分けはプログラム中でたった1個所にします。それは属するべきものの中です。

インタフェースの定義
public interface OrderProcessor {
  void processOrderNew(Order order);
  void processOrderCancel(Order order);
  void processOrderAddService(Order order);
  void processOrderRemoveService(Order order);
  void processOrderModifyService(Order order);

クライアントは、BillingSystem以外にもいろいろいるので(例えばPricingSYstem)、名前は一般的なものにしています(抽象化)。

Orderクラス内でtypeによる振り分け
public void processBy(OrderProcessor processor) {
    if (type.equals(OrderType.NEW)) {
      processor.processOrderNew(this);
    } else if (type.equals(OrderType.CANCEL)) {
      processor.processOrderCancel(this);
    } else if (type.equals(OrderType.ADD_SERVICE)) {
      processor.processOrderAddService(this);
    } else if (type.equals(OrderType.REMOVE_SREVICE)) {
      processor.processOrderRemoveService(this);
    } else if (type.equals(OrderType.MODIFY_SERVICE)) {
      processor.processOrderModifyService(this);
    }
}

すると、BillingSystemクラスのbill()メソッドは、たった1行のコードとなります。

BillingSystemクラスのbillメソッド
public void bill() {
  order.processBy(this);
}

BillingSystemクラスには、OrderProcessorインタフェースを実装するコードを記述します。

Double Dispatch

解決すべき問 どのように受け手となる2つのオブジェクトのクラスに基づいて責務を委譲するか
解決 引数にメッセージを送る。メッセージ名に自身のクラス名を付け、thisをパラメータとして渡す
分類 振舞い
関連 なし

Java/Smalltalk/C++といった多くのOO言語はシングルディスパッチを提供しています。これは、メッセージと受け取り手から実際に起動されるメソッドが決定されます。ダブルディスパッチはCLOS等の少数の言語で提供されています。メッセージ名と2つの受け取り手オブジェクトから起動されるメソッドが決まります。

Double Dispatchパターンの実装例は、デザインパターンの中のVisitorパターンで見ることができるでしょう。以下の簡単な例では、仮想のフットボールリーグのアプリケーションにおける選手の得点計算を見てみます。このアプリケーションでは、フットボール選手の階層として、Quaterback, Kicker, RunningBackなどを持ち、すべてPlayerを継承しています。選手クラスから得点を算出する機能を取り除くためにVisitorパターンを適用し、柔軟性と再利用性を高めます。

まず最初に、選手オブジェクトは"visit"可能でなくてはなりません。そこで、interfaceを定義します。

Visibableインタフェース
public interface Visitable {
  void accept(Visitor visitor);
}

Playerクラスのサブクラスはこのインタフェースを実装し、acceptメソッドを提供します。ここがDouble Dispatchの肝です。Visitorは、Playerサブクラスに属さない機能をPlayerサブクラスに実装せずに済ませます。Double Dispathで得点を計算する責務をVisitorのサブクラスに委譲し返します。

  1. 引数にメッセージを送る(この例では、visitor)
  2. メッセージのパラメータにthisを渡す
Visitableを実装したPlayer
public class Player implements Visitable {
  // コンストラクタや他のメソッド等。。。
    :
  public void accept(Visitor visitor) {
    visitor.visit(this);
  }
}

次に、Visitorの階層を実装してきます。スーパークラスVisitorに抽象メソッドvisit(Visitable)を定義します。これはPlayerクラスのacceptメソッドから起動されます。

Visitorスーパークラス
public abstract class Visitor {
  public abstract void visit(Visitable visitable);
}

実際の処理は、Visitorのサブクラスで実行されます。

Visitorの具象クラス例
public class QuaterbackVisitor extends Visitor {
  int totalScore = 0;
  public void visit(Visitable visitable) {
    Quaterback qb = (Quaterback)visitable;
    totalScore += qb.getNumberOfTDs() * 6 +
                  qb.getNumberOfCompletions();
  }
  public int getTotalScore() {
    return totalScore;
  }
}

Visitorの使用例

Visitorの使用例
public int totalQBScore(List qbs) {
  QuaterbackVisitor qbVisitor = new QuaterbackVisitor();
  Iterator iterator = qbs.iterator();
  while (iterator.hasNext()) {
    Quaterback qb = (Quaterback)iterator.next();
    qb.accept(qbVisitor);
  }
  return qbVisitor.getTotalScore();
}

Mediating Protocol

解決すべき問 2つのオブジェクトが相互作用を持ちながら独立性を保つにはどのようにコーディングするか
解決 オブジェクト間のプロトコルをリファインし、一貫した命名が使われるようにする
分類 振舞い
関連 Dispatched Interpretation、Double Dispatch

2つ以上のオブジェクトで相互にメッセージを送り合うと、相互依存性が生じ、相手なしに使用できなくなってしまう(強結合)。保守が困難になります。オブジェクト間は疎結合に保つ方がいいのですが、場合によっては相互結合を作ることもあります。Dispatched Interpretationパターンがその例です。そのときは、そのことを分かりやすくドキュメントします。最もよいドキュメントは(コメントではなく)プログラムコード自身に記述することです。Dispatched Interpretationの例にあるBillingSystemでは、相互結合で使用されるプロトコルがOrderProcessorインタフェースとして記述されています。このように相互に結合するオブジェクトでは、間にインタフェースを定義することによって相互のコミュニケーションを事前に定義します。

Super

解決すべき問 スーパークラスの振舞いをどのように起動するか
解決 メッセージの受け取り手としてsuperと明示的に指定する
分類 振舞い
関連 Extending Super、Modifying Super

superを使用すると、サブクラスとスーパークラスとが強結合となります。スーパークラスにある同名のメソッドを起動したいときに使います(サブクラスでそのメソッドをオーバーライドしていた場合)。メソッドにfinal宣言をすると、サブクラスではオーバーライドできません。

Extending Super

解決すべき問 スーパークラスのメソッドにどうやって実装を追加するか
解決 スーパークラスのメソッドをオーバーライドし、その中でsuperを使ってスーパークラスのメソッドを呼ぶ
分類 振舞い
関連 Super、Modifying Super

スーパークラスのメソッドにある振舞いを拡張する主な理由は、コードの重複を避けるためです。

Extending Superの例
public class Manager extends Employee {
  private int contractedRate;
  private boolean hasGoldenParachute;
  public Manager(String name, String ssn,
                 int _contractedRate,
		  boolean _hasGoldenParachute) {
    super(name, ssn);
    contractedRate = _contractedRate;
    hasGoldenParachute = _hasGoldenParachute;
  }
}

nameとssnは、スーパークラスEmployeeのコンストラクタを使って設定されます。

コンストラクタ以外では、デザインパターンのDecoratorでよく使われます。

DecoratorでのExtending Super使用例
public void execute(Transaction xn) {
  log.add(xn);
  super.execute(xn);
}

サブクラスでの拡張によって、トランザクション機構にチェック機能を追加しています。

Modifying Super

解決すべき問 スーパークラスを直接変更できないとき、どのようにスーパークラスの振舞いを変更するか
解決 スーパークラスのメソッドをオーバーライドし、superを指定して起動し、その結果を変更するコードを実行する
分類 振舞い
関連 Composed Method

このパターンは最後の手段として使うこと。このパターンが必要になるということは、スーパークラスをリファクタリングすべき事態を表しています。どうしてもスーパークラスを修正できないときに限って使うこと。

振舞いを修正したいスーパークラス
public Component createButtonPanel() {
  JButton button = new JButton("OK");
  button.setBackground(Color.yellow);
  JPanel pane = new JPanel();
  pane.add(button);
  return pane;
}

ボタンの色が黄色のボタンパネルを生成するメソッドがあります。これをサブクラスで修青色に修正します。

Modifying Superの適用例
public Component createButtonPanel() {
  Component pane = super.createButtonPanel();
  JButton button = (JButton)(((Container)pane).getComponent(0));
  button.setBackground(Color.blue);
  return pane;
}

本来は、スーパークラスにDefault Value Methodを適用してボタンの色を設定するようにリファクタリングするべきです。

Delegation

解決すべき問 実装を再利用する継承以外の方法は何か
解決 あるオブジェクトから別なオブジェクトへ要求を委譲する
分類 振舞い
関連 Simple Delegation、Self-Delegation

著者は、オブジェクト指向の3大概念の中でもっともきらいなものと述べています。ちなみにオブジェクト指向の3大概念とは、カプセル化、ポリモルフィズム、継承を指します。継承は、オブジェクト同士で垂直方向(継承方向)に強い結合を持たらし、上位の変更が困難になります。

単に別なオブジェクトに操作を実行させるなら、Simple Delegationパターンを使います。別なオブジェクトから元のオブジェクトにメッセージが送り返される必要があるときは、Self-Delegationパターンを使います。

Simple Delegation

解決すべき問 委譲元オブジェクトの情報が不要な場合の委譲をどうするか
解決 委譲元から委譲先へメッセージをそっくりそのまま送る
分類 振舞い
関連 Delegation、Self-Delegation
Simple Delegation例
public boolean isEmpty() {
  return stack.isEmpty();
}
public int size() {
  return stack.size();
}

Self-Delegation

解決すべき問 委譲元にメッセージを送り返す必要があるオブジェクトへどのように委譲するか
解決 委譲先に送るメッセージにおいてthisをパラメータとして渡す
分類 振舞い
関連 Delegation、Simple Delegation

Simple Delegationに比べると、オブジェクト間の結合が強くなってしまいます。

java.net.URLクラスの例
public String toString() {
  return toExternalForm(); // (1)
}
public String toExternalForm() {
  return handler.toExternalForm(this); // (2)
}
URLStreamHandlerクラスのtoExternalFormメソッド
protected String toExternalForm(URL u) {
  String result = u.getProtocol() + ":";
  if ((u.getHost() != null) &&
      (u.getHost().length() > 0)) {
        :

(1)は、Intention-Revealing Methodパターンです。

(2)は、Self-Delegationです。

Self-Delegationを過度に使うような時は、ロジックを委譲元に戻した方がよいでしょう。

Pluggable Method Name

解決すべき問 異なるメソッドを起動する別な要素は
解決 起動するメソッドの名前を保持するインスタンス変数を生成し、この変数名の末尾にMessageを付ける。Javaリフレクション機構を用いて実行する
分類 振舞い
関連 なし

このパターンを使うよりは、インタフェースを使う方がよいでしょう。リフレクションが有用なのは、メタタイプなアプリケーション(デバッガ、コンパイラ、オブジェクトインスペクタ、テストツール、プロファイラ等)です。

Collecting Parameter

解決すべき問 いくつもの操作が協調した結果であるコレクションをどのように返すか
解決 まずComposed Methodでそれぞれの操作をメソッドに分割し、各メソッドは部分集合を返し、それらを結合する
分類 振舞い
関連 Composed Method
リファクタリング前のコード
public List getScoringPlayers() {
  List scoringPlayers = new ArrayList();
  // get scoring qbs
  Iterator qbIterator = qbs.iterator();
  while (qbIterator.hasNext()) {
    Quaterback qb = (Quaterback)qbIterator.next();
    if (qb.hasTDs() || qb.hasCompletions()) {
      scoringPlayers.add(qb);
    }
  }
  // get scoring wrs
  Iterator wrIterator = wrs.iterator();
  while (wrIterator.hasNext()) {
    WideReceiver wr = (WideReceiver)wrIterator.next();
    if (wr.hasTDs() || wr.hasReceptions()) {
      scoringPlayers.add(wr);
    }
  }
  //  ... etc
  // get scoring ks
  Iterator kickerIterator = kickers.iterator();
  while (kickerIterator.hasNext()) {
    Kicker kicker = (Kicker)kickerIterator.next();
    if (kicker.hasFieldGoals() || kicker.hasExtraPoints()) {
      scoringPlayers.add(kicker);
    }
  }
  return scoringPlayers;
}

このコードは一時変数のリストに、個々の独立したタスクの結果であるプレイヤを蓄積しています。まずこのコードをComposing Methodを使い、個々のタスクを独立したメソドに分解します。

Composed Methodを適用
public List getScoringPlayers() {
  List scoringPlayers = new ArrayList();
  scoringPlayers.addAll(scoringQBs());
  scoringPlayers.addAll(scoringWRs());
  scoringPlayers.addAll(scoringKickers());
  return scoringPlayers;
}
private List scoringQBs() {
  List list = new ArrayList();
  Iterator iterator = qbs.iterator();
  whilte (iterator.hasNext()) {
    Quaterback each = (Quaterback)iterator.next();
    if (each.hasTDs() || each.hasCompletions()) {
      list.add(each);
    }
  }
  return list;
}
private List scoringWRs() {
  List list = new ArrayList();
  Iterator iterator = wrs.iterator();
  whilte (iterator.hasNext()) {
    WideReceiver each = (WideReceiver)iterator.next();
    if (each.hasTDs() || each.hasReceptions()) {
      list.add(each);
    }
  }
  return list;
}
private List scoringKickers() {
  List list = new ArrayList();
  Iterator iterator = kickers.iterator();
  whilte (iterator.hasNext()) {
    Kicker each = (Kicker)iterator.next();
    if (each.hasFieldGoals() || each.hasExtraPoints()) {
      list.add(each);
    }
  }
  return list;
}

この例ではうまく行ってますが、オブジェクトの選別操作ではしばしば既にリストに追加されているオブジェクトを知っている必要があります。また、このような部分集合の連結を用いると、性能上問題になることもあります。

そこで、集合を個々のメソッドに渡し、個々のメソッドでは直接その集合に追加していく方法があります。これが、Collecting Parameterパターンです。

Collecting Parameterパターンの適用
public List getScoringPlayers() {
  List scoringPlayers = new ArrayList();
  addScoringQBsTo(scoringPlayers);
  addScoringWRsTo(scoringPlayers);  
  addScoringKickersTo(scoringPlayers);
  return scoringPlayers;
}
private void addScoringQBsTo(List list) {
  Iterator iterator = qbs.iterator();
  whilte (iterator.hasNext()) {
    Quaterback each = (Quaterback)iterator.next();
    if (each.hasTDs() || each.hasCompletions()) {
      list.add(each);
    }
  }
}
private void addScoringWRsTo(List list) {
  Iterator iterator = wrs.iterator();
  whilte (iterator.hasNext()) {
    WideReceiver each = (WideReceiver)iterator.next();
    if (each.hasTDs() || each.hasReceptions()) {
      list.add(each);
    }
  }
}
private void addScoringKickers(List list) {
  Iterator iterator = kickers.iterator();
  whilte (iterator.hasNext()) {
    Kicker each = (Kicker)iterator.next();
    if (each.hasFieldGoals() || each.hasExtraPoints()) {
      list.add(each);
    }
  }
}

状態に関するパターン

オブジェクトの属性(インスタンス変数)によって、オブジェクトの「状態」が決まります。ここで扱うパターンは実装についてなので、設計について扱うステートパターン類は含みません。

インタンス変数の区分

また、一時変数に関するパターンもこの章で紹介します。一時変数は、設計時には扱われません。

Common State

解決すべき問 状態をどのように表現するか
解決 クラスの定義でインスタンス変数を提供する
分類 状態
関連 Variable State

ある時点でのオブジェクトの属性のスナップショットが「状態」です。状態を表す属性が1つも無ければ、そのオブジェクトは単なる関数の集合です。属性の実装として、Javaではインスタンス変数を用います。

また、状態はオブジェクトのテストに関わります。メッセージを送った後、2つの出力をテストします。1つは戻り値でもう1つがオブジェクトの状態です。

Variable State

解決すべき問 単一クラスのインスタンスで状態の表現をどのように変えるか
解決 HashMapを使って属性をキー・値の組合せで保持する。属性へのアクセスをgetPropertyメソッドを介して行い、属性の変更をsetPropertyメソッドで行う。
分類 状態
関連 Common State

インスタンス同士で属性が(属性の値がではなく)違うクラスを表現します。このパターンは滅多に使用しません。

java.util.PropertyクラスがVariable Stateパターンの例です。

Explicit Initialization

解決すべき問 インスタンス変数をどのようにデフォルト値に初期化するか
解決 インスタンス変数を宣言する時に、初期値を代入する
分類 状態
関連 Lazy Initialization

Javaは、インスタンス変数を代入なしに宣言すると、暗黙的に初期値が代入されます。しかし、これに頼ってはいけません。

よって、Lazy Initializationを使っていない全ての変数は、例え初期値がデフォルト値と同じであっても明示的に初期化します。

Explicit Initializationの適用
public class Employee {
  int hoursPerWeek = 40;
  boolean isPartTime = false;
  // ...
}

インスタンス変数がコンストラクタでは初期化されず、インスタンス生成後、別なメソッドで初期化される場合は、インスタンス変数を明示的にnullに初期化します。

Explicit Initializationでnullを適用する場合
public class Employee {
  private Beneficiary beneficiary = null;
  public Employee() {
  }
  public void setBeneficiary(String name) {
    beneficiary = new Beneficiary(name);
  }
}

初期化する値が不明のときは、Default Value ConstantやDefault Value Methodを使います。

Lazy Initialization

解決すべき問 インスタンス変数をどのようにデフォルト値に初期化するか
解決 インスタンス変数がGetting Methodの中でアクセスされる度に値をテストし、未初期化ならば初期値をセットする
分類 状態
関連 Explicit Initialization

性能上、初期化処理をインスタンス変数が最初に使用される時まで遅らせます。そのため"lazy"と名前がつけられています。

Lazy InitializationはSetting Methodの中で行われます。

LazyInitialization
public String getTitle() {
  if (title == null) {
    title = "Grunt Level I";
  }
  return title;
}

この例なら、Default Value Methodでも実現できます。アクセスの度に判定が行われるので、性能を気にするかもしれません。Lazy Initializationの真価は、オブジェクトの初期化を前もって行うコストが大きい時に発揮します。あまり頻繁に属性がアクセスされないときに適しています。

例:全従業員の写真が格納されているシステムにおいて、給与計算時にはこのイメージをメモリに展開する必要がありません。

Lazy Initializationを使うときは、合わせてIndirect Variable Acceessを使わなければなりません。そこで、Lazy Initializationを使うオブジェクトはその全ての属性にIndirect Variable Accessを使うべきです。

Default Value Constant

解決すべき問 変数のデフォルト値をどのように表現するか
解決 デフォルト値を指定するConstantを提供する。このConstantで変数を初期化する
分類 状態
関連 Default Value Method、Explicit Initialization、Lazy Initialization

インスタンス生成時にデフォルト値を与えるならば、Explicit Initializationを用います。ただし、途中でリセットする必要がある時はこれが使えません。この場合、本当はDefault Value Methodを適用すべきなのですが、一般にはDefault Value Constantが用いられています。しかし、このパターンは推奨しません。

Default Value Constantの例
private static final int defaultFullTimeHoursPerWeek = 40;
private int hoursPerWeek = defaultFullTimeHoursPerWeek;

サブクラスでオーバーライドできないのが問題です。これはDefault Value Methodを適用すると解決できます。

Default Value Method

解決すべき問 変数のデフォルト値をどのように表現するか
解決 デフォルト値を返却するメソッドを提供する
分類 状態
関連 Default Value Constant、Constant
Default Value Methodの例
public int getDefaultHoursPerWeek() {
  return 40;
}

これを、Constantを使ってさらに一歩進めると、

Constantを使ったDefault Value Methodの例
public int getDefaultHoursPerWeek() {
  return defaultFullTimeHoursPerWeek;
}

サブクラスでオーバーライドできます。また、Lazy InitializationやExplicit Initializationと一緒に使うことができます。

Explicit Initializationと合わせて使ったDefault Value Methodの例
private int hoursPerWeek =
  getDefaultHoursPerWeek();
Lazy Initializationと合わせて使ったDefault Value Methodの例
public int getHoursPerWeek() {
  if (hoursPerWeek == 0) {
    hoursPerWeek = getDefaultHoursPerWeek();
  }
  return hoursPerWeek;
}

Constant

解決すべき問 定数をどうやって表現するか
解決 finalクラス変数を宣言し、定数として使用する
分類 状態
関連 Constant Method

このパターンはJavaSoftによって定められた標準で、多くのコードが従っていますが、推奨いたしません。

Javaにはconst修飾子がないので、代りにfinalを使用します。Javaのswitch構文は貧弱で、case文においてint型かそれより小さい基本型の定数式しか使えません。そのため、Constantパターンが使われています。

Constantの例
public static final char SPACE = ' ';
public static final String OPEN_FILE = "Open File";
public static final Point ENDPOINT = new Point(100, 120); // (1)

(1)は、オブジェクトの内容ではなく、参照がconstantです。したがって、オブジェクトの内容は一定ではありません。

final宣言されたオブジェクトの内容の変更
ENDPOINT.y = 222;  // OK
ENDPOINT = new Point(2,3);  // エラー

オブジェクトの内容を変更できないようにするには、そのオブジェクトをStringクラスのようにイミュータブルに設計します。例えば、コンストラクタでイミュータブルフラグを提供する等があります。

Constant Medhod

解決すべき問 変数のデフォルト値をどうやって表現するか
解決 定数値を返却するアクセッサメソッドを提供する
分類 状態
関連 Constant

「定数」は興味深いことにそれが一定であることが滅多にありません(もちろんπやアボガドロ数のような数学的定数は不変です)。例えば、U.S.A.の州の数は、100年後も50でしょうか?会社の資格レベルは何年も固定でしょうか?

定数の変更は、Constantパターンでも十分ですが、いつか固定の数ではなくなるかもしれません。例えば、今日は週の労働時間が40Hでも、明日は雇用種類によって異なった計算結果になるかもしれません。

定数が計算値に変わったとき、Constantパターンを使用していたら、全てのクライアントコードを変更する羽目になります。

Constant Methodの例
public int hoursPerWeek() {
  return 40;
}
public String disclaimer() {
  return "Systems Systems, Inc. claims no liability " +
    "for injuries sustained while using this product.";
}

Constantパターンに対するメリットは以下のとおりです。

Constatn Methodの命名方法は、Getting Methodを適用します。

Constant Pool

解決すべき問 複数のクラスで必要な共通の定数の集りをどのように共有するか
解決 interfaceを定義する。定数をinterface中に宣言する。定数を必要とするクラスでinterfaceをimplementsする
分類 状態
関連 Indirect Variable Access、Lazy Initialization

複数のクラスで使用される定数の一群があります。それがもはやどのクラス単独には属さないような場合は、独立させます。便宜上の理由だけで無関係な定数を一つに集めてはいけません。

Constant Poolの定義
public interface PayrollConstants {
  int HOURS_PER_WEEK = 40;
  int HOURS_PER_YEAR = 2000;
  double OVERTIME_RATE = 1.5;
  int WEEKS_IN_PAY_PERIOD = 2;
}
Constant Poolの利用例
public class ConstantPool implements PayrollConstants {
  public static void main(String[] args) {
    new ConstantPool();
  }
  public ConstantPool() {
    double hourlyRate = 30.0;
    System.out.println("Pay for rate of " +
      hourlyRate + " = " +
      calculatePay(hourlyRate));
  }
  public double calculatePay(double baseRate) {
    return baseRate *
      WEEKS_IN_PAY_PERIOD *
      HOURS_PER_WEEK;
  }
}

Constant Poolパターンをもっと効果的に使うには、Enumerated Constantsと合わせ、型の安全性を保証します。

Direct Variable Access

解決すべき問 インスタンス変数の値をどのように読み書きするか
解決 インスタンス変数を直接読み書きする
分類 状態
関連 Indirect Variable Access、Lazy Initialization

ここでの"variable access"とは、オブジェクト内部におけるアクセスを示します。インスタンス変数を定義したオブジェクト内のメソッドコード中では、直接インスタンス変数にアクセスするか、Getting Methodを介して間接的にアクセスするのか決めなくてはなりません。どちらが正しいというものではありません。

Direct Variable Accessを使う状況は、以下のとおりです。

アプリケーションレベルのUIオブジェクトがその典型例です。

Direct Variable Accessの使用例
class Employee {
  private String lastName;
  private String firstName;
  private int hoursWorkedPerWeek;
  // ...
  public void setHoursWorkedPerWeek(int hours) {
    hoursWorkedPerWeek = hours; // (1)
  }
  public int getAnnualHours() {
    return hoursWorkedPerWeek * 50; // (2)
  }
  public String toString() {
    return lastName + ", " + firstName; // (3)
  }
}

(1)、(2)、(3)がDirect Variable Accessです。(2)はSetting Methodです。Setting Methodでは、直接変数に値を代入する必要があります。

Indirect Variable Access

解決すべき問 インスタンス変数の値をどのように読み書きするか
解決 Getting MethodとSetting Methodを用いてインスタンス変数の値を読み書きする
分類 状態
関連 Direct Variable Access、Lazy Initialization

オブジェクトの内部からインスタンス変数をアクセスする際は全て、Getting MethodとSetting Methodを使います。このパターンは、Direct Variable Accessを使った問題点を解決します。

また、一時変数とインスタンス変数とを混同することがなくなります。

Indirect Variable Accessの例
class Employee {
  private String lastName;
  private String firstName;
  private int hoursWorkedPerWeek;
  // ...
  public int getHoursWorkedPerWeek() {
    return hoursWorkedPerWeek; // (1)
  }
  public int getAnnualHours() {
    return getHoursWorkedPerWeek() * 50; // (2)
  }
  public String toString() {
    return getLastName() + ", " + getFirstName();
  }
}

(1)はGetting Methodなので直接変数にアクセスします。(2)、(3)はIndirect Variable Accessの例です。

Getting Method

解決すべき問 インスタンス変数へのアクセスをどのように提供するか
解決 インスタンス変数を返却するメソッドを提供する。get+インスタンス変数名()と命名する
分類 状態
関連 Setting Method

クライアントオブジェクトからアクセスされるインスタンス変数にはGetting Methodを提供します。クラス設計時には、どの属性を外部に公開(Getting Methodを介して)するか決定します。全てのGetting Methodを盲目的にpublicにしてはいけません。命名は、インスタンス変数名の最初の1文字を大文字にし、'get'を接頭辞に付けます。

インスタンス変数 Getting Method名
newEmployees getNewEmployees()
ytdAmount getYtdAmount()

Setting Method

解決すべき問 インスタンス変数をどのように変更するか
解決 インスタンス変数を設定するメソッドを提供する。set+インスタンス変数名()と命名する
分類 状態
関連 Getting Method

クライアントオブジェクトがオブジェクトの状態を変更することを可能にします。インスタンス変数はprivateにします。全ての属性についてpublicなSetting Methodを提供してはいけません。頑丈でかつ再利用できるオブジェクトを作るには、オブジェクトの完全性を下げようとする外部からの影響を完全に制御下に置かなくてはなりません。Setting Methodは蟻の一穴となるかもしれません。

Indirect Variable Accessを使っているなら、全ての属性についてSetting Methodを宣言するでしょう。このとき、できるだけprivateかまたはprotectedとします。

命名は、インスタンス変数名の最初の1文字を大文字にし、'get'を接頭辞に付けます。

インスタンス変数 Setting Method名
newEmployees setNewEmployees()
ytdAmount setYtdAmount()

Collection Accessor Method

解決すべき問 コレクションを保持するインスタンス変数へのアクセスをどのように提供するか
解決 JDK1.1:委譲メッセージを介してのみコレクションへアクセスを許す。JDK1.2:Collectionsクラスのunmodifiableラッパーを使用する
分類 状態
関連 Getting Method

JDK1.1

コレクションはprivateとします。不幸にもコレクションを直接返却することが一般的に使われていますが、コレクションへの直接アクセスは出来ないようにします。代りにコレクションに対してクライアントが行うであろう操作をメソッドとして提供します。このメソッドはある種のSimple Delegationパターンになります。ただし、それぞれのメソッドには、特定の状況に合わせた名前を付けるようにします。

private Vector withdrawals = new Vector();
// ...

public Enumeration getWithdrawals() {
  return withdrawals.elements();
}
public int getNumberOfWithdrawals() {
  return withdrawals.size();
}

コレクションに対する枚挙を提供するには、コレクション自身を返却するのではなく、コレクションのEnumerationを返却するようにします。

Enumerationはsize()メソッドを提供していないので、これを返却するメソッドを追加することがあるでしょう。

JDK1.2

例えば、Listが返すIteratorオブジェクトにはremove()メソッドがあります。これを外部へ渡すのは避け、代わりに不変なラッパーを使います(Collectionsクラスのstaticメソッドを利用)。このラッパーは、コレクションの変更を禁止されています(UnsupportedOperationMethodをスローします)。

private List withdrawals = new ArrayList();
//...
public List getWithdrawals() {
  return Collections.unmodifiableList(withdrawals);
}

Collection Accessor Methodのjavadocコメントには、変更不可能なコレクションを返却する旨を明記して下さい。

Enumeration Method

解決すべき問 コレクションの要素へ安全で汎用的なアクセスをどのように提供するか
解決 引数にクロージャを取るメソッドを提供する。コレクションをイテレートし、コレクションの各要素についてクロージャにメッセージを送る
分類 状態
関連 Collection Accessor Method

Collection Accessor Methodを適用すると、クライアントにコレクションを変更されないようにします。ただし、クライアントがコレクションの個々の要素を操作できるようにします。この時、全てのクライアントでは、コレクションの枚挙をループして個々の要素を引き出してから望みの操作を実行するコードを書かねばなりません。

そこで、クロージャを使ってコレクションの枚挙を元のオブジェクト内部にカプセル化します。クロージャとは関数の定義とそれを実行する環境を合わせたもので、関数を動的に生成することが出来ます。Javaではクロージャを匿名インナークラスを使って実装します。

Enumeration Methodはオブジェクトの内部にあるコレクションを枚挙する機能を提供します。個々の要素オブジェクトについて、クロージャへパラメータに要素オブジェクトを入れたメッセージを送り返して操作を実行します。

Closureインタフェース
public class Closure {
  public void exec(Object item) {}
}
コレクションの操作を行うEnumeration Method
public void dependentsDo(Closure closure) {
  Iterator iterator = dependents.iterator();
  while (iterator.hasNext()) {
    Dependent dependent = (Dependent)iterator.next();
    closure.exec(dependent);
  }
}

命名として、コレクション名+"Do"を使います。

クライアントコード
employee.dependentsDo(new Closure() {
    public void exec(Object item) {
      Dependent dependent = (Dependent)item;
      System.out.println(dependent.getName() +
        " is " + dependent.getAge() + " years old.");
    }
  });

Enumerated Constants

解決すべき問 安全なC言語のenumのような能力をどうやって提供するか
解決 列挙を表現する特別なクラスを作る
分類 状態
関連 Constant

Employeeクラスに、正規雇用かパートかを表す状態を属性として持たせたとします。

Employeeクラスに定義した状態を表す定数例
public static final int FULL_TIME = 1;
public static final int PART_TIME = 0;

このとき、状態を判別するクライアント側のコードは次のようになります。

定数を利用する例
if (employee.getEmployeeStatus() == Employee.FULL_TIME) {
  // ...
}

ここで、別なクライアント側のコードで、たまたま定数がint型であることと、その値が0と1であることを利用して、次のようなボーナス額を計算していました。

定数の実装に依存したクライアントコード例
double bonus = employee.getEmployeeStatus() * 
               (employee.getBaseSalary() * .1);

これは確かに「動き」はするプログラムです。しかし、後に状態を表す属性の実装方法をint型ではなくchar型に変更し、正規雇用を'F'、パートを'P'、さらにフレックス制適用を追加し'X'としました。この時、上記のボーナス計算プログラムはエラーにはならず、びっくりするほど高額のボーナスを与えてくれるようになります。実装が外部にさらされ、カプセル化を破壊しています。

これとは別に、状態を設定するメソッドsetEmployeeStatusを考えると、引数で渡される値(int型であったりchar型であったり)が状態として定義している範囲に入っているかをテストしなくてはなりません。このテストには、Guard Clauseを適用することが出来ますが、それぞれのメソッドにコードを追加していくので、コードが増え、テストと保守も増えます。それでも利用者が誤って使うことは防げません。それに、エラーは実行時よりもコンパイル時に捕まえたいところです。残念ながらJavaにはC/C++のenum型が無いです。そこで、Enumerated Constantsを実装するために再利用可能なスーパークラスを作ります。

Enum
package enum;
import java.util.List;
import java.util.ArrayList;

public class Enum {
  protected static int numberOfEnums = 0;
  protected static List list = new ArrayList();
  private int ordinal;
  private String name;
  protected Enum(String _name) {
    name = _name;
    ordinal = numberOfEnums++;
    list.add(this);
  }
  public static toString() {
    return name;
  }
  public int getOrdinal() {
    return ordinal;
  }
  public static Enum get(int ordinal) {
    return (Enum)list.get(ordinal);
  }
  public static int size() {
    return numberOfEnums;
  }
}

これを継承していろいろな種類の列挙クラスを定義します。

MaritalStatus
package member;

public class MaritalStatus extends enum.Enum {
  private MaritalStatus(String maritalStatus) {
    super(maritalStatus);
  }
  public static final MaritalStatus SINGLE =
      new MaritalStatus("Single");
  public static final MaritalStatus MARRIEDE =
      new MaritalStatus("Married");
  public static final MaritalStatus DIVORCED =
      new MaritalStatus("Divorced");
  public static final MaritalStatus SEPARATED =
      new MaritalStatus("Separated");
  public static final MaritalStatus WIDOWED =
      new MaritalStatus("Widowed");
}

MaritalStatusを利用するクライアントは、MaritalStatus.MARRIEDという形で定数を利用します。ただし、new MaritalStatus("HOPELESS")のように新たなインスタンスは生成できません(コンパイルエラー)。

Boolean Property-Setting Method

解決すべき問 booleanのプロパティをどのように設定するか
解決 2つのメソッドを作る。1つはプロパティを真に設定し、もう1つは偽に設定する。どちらも引数は取らない
分類 状態
関連 Setting Method

boolean型の属性に対するSetting Methodを、他の型の属性と同様に扱って、setBoolValue(boolean)のように定義してしまいがちです。しかし、boolean属性は、trueとfalseの2つしか取りませんし、型が変更になることが多発します。Boolean Property-Setting Methodパターンは、boolean属性の変更をカプセル化します。

public void setFullTime() {
  fullTime = true;
}
public void setPartTime() {
  fullTime = false;
}

このパターンのさらによい点は、コードを分かりやすくします。employee.setFullTime(false)に比べ、コードが何をするものか明瞭になっています。

別な例
public void setLocked() {
  locked = true;
}
public void setUnlocked() {
  locked = false;
}

Role-Suggesting Instance Variable Name

解決すべき問 インスタンス変数をどのように命名するか
解決 どのように実装したかではなく、オブジェクトの属性として果たす役割に従って命名する
分類 状態
関連 Role-Suggesting Temporary Variable Name

よい命名は、保守可能なソフトウェアを開発する上でとても重要です。Composed Methodでさえ、無意味なメソッド名に対しては無力です。保守上、最初にすることはコードが何をしているか理解することです。ロジックに手を付ける前に、インスタンス変数名をより意味ある名前に付け変えることをよく行います。

インスタンス変数は、オブジェクトモデリングとつながっているので実装とは離れて命名することが重要です。Javaで、集合を保持するインスタンス変数があるとき、java.util.Listを使って実装し、それにproductsListと命名する傾向があります。これでは、実装をHashMapに変更したら、インスタンス変数名も変更しなければなりません。そうでないと、コードが誤解を生むものになってしまいます。

この場合の解決策は、productsと命名することです。同様に、idNumberではなくid、filenameStringではなくfilenameとします。

Temporary Variable

解決すべき問 メソッド内で、後で使う値をどのように保持するか
解決 ローカルスコープの変数を宣言し、それに値を代入する
分類 状態
関連 Collecting Temporary Variable、Caching Temporary Variable、Reusing Temporary Variable、Explaining Temporary Variable

存在するスコープがメソッドまたはブロックである変数で、スタック変数とも言われます。オブジェクト指向設計ではモデリング要素として登場していません。変数を、使用する直前で宣言する規約はJavaでも有効ですが、Composed Methodを適用すれば1つのメソッドは10行程度に収まるので、さほど問題ではありません。むしろ重要なことは、メソッドで最初に宣言する時に、初期値を割り当てることです。ただし、不必要なオブジェクトの生成をしないように注意します。オブジェクトの生成はコストがかかるし後に異なる型のオブジェクトを生成するかもしれません。このような時は、nullを割り当てます。

以下、一時変数を使用する4つのパターンを述べます。

Collecting Temporary Variable

解決すべき問 メソッド内で、後で使う複数の値をどのように集めるか
解決 集めた複数の値を保持する一時変数(Temporary Variable)を使用する
分類 状態
関連 Temporary Variable

このパターンはよくListやVectorといったコレクションとともに使います。1つ以上の式の結果であるオブジェクトをコレクションに追加し、後ろでこの変数をイテレートしたりメソッドの戻り値として使用します。Composed Methodを十分に使いこなしていれば、滅多にこのパターンを必要とすることはないでしょう。

protected void writeRequiredFieldsFunction(PrintWriter toClient) {
  toClient.println("function validate(form)");
  toClient.println("{");
  
  Vector fields = new Vector(); // (1)
  fields.addElement("userId"); // (2)
  fields.addElement("password"); // (2)
  writeRequiredFieldCode(toClient, "form", fields); // (3)
  toClient.println("   return true");
  toClient.println("}");
}

(1)はCollecting Temporary Variableとして使うVector型のfieldsを宣言しています。

(2)は後で使うオブジェクトを追加しています。

(3)で呼び出したメソッドで、fieldsを使用しています。

なお、本来この例のメソッドは、リファクタリングすべきです。

Caching Temporary Variable

解決すべき問 メソッドの性能をどのように改善するか
解決 コストのかかる式を一時変数に割り当て、キャッシュとする。メソッドの残りではこの変数を使用する
分類 状態
関連 Temporary Variable

性能は向上しますが、可読性は悪化しません。高価な演算結果(例えば、SimpleDateFormatクラスのformatメソッド)を繰り返し使うために、Caching Temporary Variableを使用します。

Explaining Temporary Variable

解決すべき問 メソッド内の複雑な式をどのように簡単化するか
解決 複雑な式を部分式に分割し、部分式の結果を一時変数に割り当て、Role-Suggesting Temporary Variable Nameに従って命名する
分類 状態
関連 Temporary Variable

コードを記述する重要な目標は理解しやすくすることです。Composed Methodは、複雑なメソッドの意図を明確にしますが、1つの式までメソッド化するのは行き過ぎです。その式が1回しか使われないなら、メソッド化するべきではありません。そのような時は、Explaining Temporary Variableを適用します。

複雑な式を、理解しやすい部分式に分割し、その値を一時変数に置きます。

複雑な式の例
int days = (int)((laterDate.getTime() - earlierDate.getTime()) /
                 (60 * 60 * 24 * 1000));
部分式に分割した例
long msDifference = laterDate.getTime() - earlierDate.getTime();
long msInADay = 60 * 60 * 24 * 1000;
int days = (int)(msDifference / msInADay); 

Reusing Temporary Variable

解決すべき問 式の値が変わるかもしれないとき、メソッド内で式の結果を複数回使用するにはどうするか
解決 式の結果を一時変数に代入する。その変数をメソッドの残りを通して再利用する
分類 状態
関連 Temporary Variable

複数回実行したときに、同じ値を返さない式があります。例えば、new Date()やiterator.next()やtokenizer.nextToken()などです。この式の値を保持する一時変数が、Reusing Temporary Variableです。

Role-Suggesting Temporary Variable Name

解決すべき問 一時変数をどのように命名するか
解決 計算の中でそれが果たす役割にちなんで名付ける
分類 状態
関連 Temporary Variable

Role-Suggesting Instance Variable Nameパターンとほとんど同じです。一時変数はより戦術的で実装特有です。短い省略した名前はよくありません。タイプ量を少し減らしても、今後の保守にコストがかかってしまいます。なぜなら、

コレクション

JDK1.2からコレクション・フレームワークが導入され、オブジェクトの集合を表現するクラスが提供されています。特によく定義されたインタフェースとその実装で構成されています。また、同期/不変といった付加機能を持つラッパーもあります。検索/ソートといったアルゴリズムも提供されています。しかも、JDK1.1のVector/Hashと互換性を保っています。

Collection

解決すべき問 オブジェクトの集合をどう表現するか
解決 コレクションを使う
分類 コレクション
関連 List、Array、Map、Set

Java 2には2つの継承階層があります。1つは独立したオブジェクトの集合(collection)、もう一つはオブジェクト間のマッピングに基づく集合(map)です。

  1. 様々なコレクションの集合の実装に共通するインタフェース
  2. 具体的な実装、インスタンス化され、データ構造として使われるクラス
  3. アルゴリズムの集合、どのコレクションにも使えるコード

クラス

フォーマット

参考文献

[1]Langr,Jeff. Essential Java style : patterns for implementation. Upper Saddle River,NJ:Prentice Hall Inc., 2000.
[2]Beck,Kent. Smalltalk Best Practice Patterns. Upper Saddle River,NJ:Prentice Hall Inc., 1996.