デザインパターンは,変化に強いコードを書くための戦略を集めたカタログです.
カタログであるが故に,自分の欲しいものが必ずしも見つかるとは限りません.
よって,「どのデザインパターンを使おうかなぁ」から始まるコーディングは不適切なやり方と言えます.
ボブおじさんも,次のように言っています.
デザインパターンを初めから意図的に取り込むといったやり方を私は勧めない.私が好む方法は,必要に応じてコードを進化させるやり方だ.(中略)改善する過程で,そのコードがある特定のデザインパターンに近づいていることに気がつくことがある.(中略)こうして,コードがデザインパターンに回帰していくのだ.
ーロバート・C・マーチン,アジャイルソフトウェア開発の奥義,24章冒頭ー
では,ボブおじさんの言う『改善』はどのように始まるのでしょうか?
このエントリでは,『改善』への出発点を,
- 共通部分と特異部分を分ける作業
として,コードを進化させる過程を見ていきたいと思います.
勢いで書き下ろしたコード
整数のリストを引数とし,その中の偶数,または奇数の要素のみを取り出すメソッドを書きました.
public List<Integer> extractEven(List<Integer> src) { List<Integer> evens = new LinkedList<>(); for (final int e : src) { if (e % 2 == 0) { evens.add(e); } } return evens; } public List<Integer> extractOdd(List<Integer> src) { List<Integer> odds = new LinkedList<>(); for (final int e : src) { if (e % 2 == 0) { odds.add(e); } } return odds; }
特異部分を取り出す
extraceEven と extractOdd は,ほとんどのコードが共通しています.
コードの特異部分は,for分の内側にあるif分の『条件』だけです.
特異な部分を,切り分けて,関数の外に追い出すのがオブジェクト指向です.
そこで,『条件』自体をオブジェクトにして関数の引数で渡すという改善をします.
public interface Condition<T> { boolean satisfies(T e); }
public class IsEven implements Condition<Integer> { @Override public boolean satisfies(Integer e) { return e % 2 == 0; } }
public class IsOdd implements Condition<Integer> { @Override public boolean satisfies(Integer e) { return e % 2 != 0; } }
public <T> List<T> extract(List<T> src, Condition<T> condition) { List<T> extracted = new LinkedList<(); for (final T e : src) { if (condition.satisfies(e)) { extracted.add(e); } } return extracted; }
抽出条件を引数に渡して,extract 関数の動作を変えることができます.
public <Integer> extractEven(List<Integer> src) { return extract(src, IsEven); } public <Integer> extarctOdd(List<Integer> src) { return extract(src, IsOdd); }
特異部分の『くくり出し』で変更に強くなる
今回は,if文の条件を特異部分として捉えるという極端な例を示しました.
ただ,このエントリで行ったことと,デザインパターンがやろうとしていることは本質的に同じです.
つまるところ,デザインパターンは
- 特異部分を取り出し方
- 外部からの与え方
のカタログだからです.
カタログに無いような取り出し方であっても,それが必要ならばどんどん外部から与えるべきです.
変化に強いシステムを作りには,変化を外に追い出すほかに方法はありません.