オブジェクト指向言語の参考書では
- フィールドを 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 が悪だとは思いませんが,それが「本当に必要か」を考えることには意義があります.
不必要な public フィールドがあると不必要なモジュールの依存関係が生まれて,システムの柔軟性が損なわれるからです.
「getter を private にする」というのはコンパイラを使った静的コード解析のようなものです.
リファクタリングのきっかけとして,「お手軽コード解析」くらいの気持ちでやってみるのはアリだと思います.