突っ走り書き

見せるほどのものでは..

リファクタリングのために getter を private にしてみれば?

オブジェクト指向言語の参考書では

  • フィールドを private に
  • フィールドの取得は getter を使って

が基本のように書いていることが多いように思います.
でも,その getter は本当に必要ですか?
もしかしたら getter がクラス間の関係を複雑にしているかもしれません.
このエントリでは getter を private にすることで設計を見直す方法を提案します.

Team クラスを例に

Team クラスの getter を private メソッドにします.

public class Team {
   private Set<Member> members;

   // こんなふうに
   // public Set<Member> getMembers() {
   private Set<Member> getMembers() {
      return members;
   }
}

今まで public だったメソッドが private になったので,コンパイルでコケました.
エラーの箇所を調べてみると,Team クラスを文字列化する ConciseTeamFormatter で getMembers を呼び出していることが分かります.

public class ConciseTeamFormatter {
   public String format(Team team) {
      StringBuilder sb = new StringBulder();
      // --- !! ここでエラー !! ---
      Set<Member> members = team.getMembers();
      // --- !! ここでエラー !! ---

      for (final Member m: members) {
         sb.append(m).appned(" ");
      }

      return sb.toString();
   }
}

少考: private なフィールドを見せびらかすのは如何なものか

getter を呼び出した側では,private なフィールドを取り出して何をしてるんでしょう?
同じ処理を getter を使わないで実現する方法はないでしょうか?

ConciseTeamFormatter#format は Team クラスに移動

シンプルなアイディアは,ConciseTeamFormatter クラスの format メソッドを Team クラスに移動することです.

public class Team {
   private Set<Member> members;

   // public Set<Member> getMembers() {
   private Set<Member> getMembers() {
      return members;
   }

   public String format() {
      StringBuilder sb = new StringBulder();

      for (final Member m: members) {
         sb.append(m).appned(" ");
      }

      return sb.toString();
   }
}

これで,コンパイルは成功し,ついでに Team#getMembers を削除できます.

さらに小考: Team クラスは頑張りすぎかも

ところで,現時点での Team クラスは

  • メンバーの管理
  • メンバーの文字列化

の2つの責務を持っています.
そこで,Team クラスが単一の責任を全うできるように,文字列化を TeamFormatter インターフェースに委譲します.

public class Team {
   private Set<Member> members;

   public String format(TeamFormatter f) {
      return f.format(members);
   }
}

interface TeamFormatter {
   String format(Team t);
}

1つ前のコードと同様に Team クラスは format メソッドをもちますが,実際の処理は引数の TeamFormatter に委譲しています.
format メソッドの引数に指定される TeamFormatter の実装クラスは,
例えば,次のように実装します.

public class ConciseTeamFormatter implements TeamFormatter {
   @Override
   public String format(Set<Member> members) {
      // 実装コード...
   }
}

public class VerboseTeamFormatter implements TeamFormatter {
   @Override
   public String format(Set<Member> members) {
      // 実装コード...
   }
}

まとめ: getter を private にすることから始めてみる

このエントリでは

  • getter を private にする
  • コンパイルエラーの箇所を見つける
  • getter を必要としない設計に改良する
  • コンパイルを通して,getter を削除

しました.さらに,

  • 多くの責務をもつクラスを分割

して,柔軟なクラス関係になるようにリファクタリングをしました.

getter が悪だとは思いませんが,それが「本当に必要か」を考えることには意義があります.
不必要な public フィールドがあると不必要なモジュールの依存関係が生まれて,システムの柔軟性が損なわれるからです.

「getter を private にする」というのはコンパイラを使った静的コード解析のようなものです.
リファクタリングのきっかけとして,「お手軽コード解析」くらいの気持ちでやってみるのはアリだと思います.