テスト駆動型のアプローチに基づいた、ソースコードのリファクタリングを題材にして、非常に便利で有名なデザインパターン「Commandパターン」を紹介します。
-
- Page 1
Commandパターンの例
皆さんのシステムには下記のリスト1に示すserviceのようなメソッドがありますか? このようなメソッドを、私は多くのシステムで目にしてきました。なぜこうなるのかは知りませんが、しばしばこういったメソッドのコードを変更したり、新しい種類のリクエストのサポートを追加したりする羽目になります。変更する際にはコードを壊さないように、メソッドの動作を単体テストで確認するようにしています。
しかし、リスト1のようなコードのテストを作成するには一体どこから手をつけたらよいでしょうか。
public class LibrarySystem { ... public void service(Request request) { String command = request.getParameter("command"); if (command.equals("return")) { String barCode = request.getParameter("barCode"); Date date = new Date(); String branchName = request.getParameter("branch"); Branch branch = findBranch(branchName); checkIn(barCode, date, branch); } else if (command.equals("checkout")) { String barCode = request.getParameter("barCode"); Date date = new Date(); String patronId = request.getParameter("id"); checkOut(patronId, barCode, date); } else if (command.equals("newBranch")) { String branchName = request.getParameter("branch"); addBranch(branchName); } else if (command.equals("addBook")) { String branchName = request.getParameter("branch"); String author = request.getParameter("author"); String title = request.getParameter("title"); String classification = request.getParameter("classification"); String year = request.getParameter("year"); Book book = new Book(author, title, classification, year); String copyNumberParm = request.getParameter("copyNumber"); int copyNumber = 1; if (copyNumberParm != null) { copyNumber = Integer.parseInt(copyNumberParm); } addNew(branchName, book, copyNumber); } else if (command.equals("addPatron")) { String name = request.getParameter("name"); String id = request.getParameter("id"); Patron patron = new Patron(name, id); add(patron); } } public void add(Patron patron) { ... } ...
switchステートメントやif/elseステートメントがあるときはポリモーフィズムを採用するチャンスなのですが、今回はそうではなく、テスト駆動型のアプローチに基づき、単体テストに適した形に変更しながら、コードをゆっくりと確実にリファクタリングしていくことにします。
まずはaddPatronコマンドから始めましょう。このコードの最初の単体テストをリスト2に示します。このテストではまさに、コードをスタンドアロンユニットとして切り離すことを試みています。具体的には、serviceメソッド内のコードによって、LibrarySystemのadd(Patron)メソッドが適切な値を含むPatron引数を使って呼び出されることを確認します。最終的には、この方法でテストすることが最適とはおそらく言えないでしょうが、とりあえずはこれで良しとします。
public class LibrarySystemTest { @Test public void addPatron() { final List<Patron> added = new ArrayList<Patron>(); LibrarySystem system = new LibrarySystem() { @Override public void add(Patron patron) { added.add(patron); } }; Request request = new Request(); request.setParameter("command", "addPatron"); request.setParameter("name", "patronName"); request.setParameter("id", "patronId"); system.service(request); assertEquals(1, added.size()); assertEquals("patronName", added.get(0).getName()); assertEquals("patronId", added.get(0).getId()); } }
単体テストがうまく機能することがわかったら、コードを壊していないことを慎重に確認しながら、serviceメソッド内のaddCommandのコードを変更します。変更後のelse以降のコードは以下のようになります。
} else if (command.equals("addPatron")) { AddPatronCommand addPatron = new AddPatronCommand(request, this); addPatron.execute(); }
AddPatronCommandクラスは、一部のコードを新しいクラスに移動して作成したものです(リスト3を参照)。一時的に、LibrarySystemの参照をこのクラスのコンストラクタに渡しました。その結果、LibrarySystemとAddPatronCommandが互いに相手の存在を認識するという密結合の関係になってしまいました。これは望ましい状況ではないので、コードをチェックインするまでに何とか解決しようと思います。
public class AddPatronCommand { private String name; private String id; private final LibrarySystem system; public AddPatronCommand(Request request, LibrarySystem system) { this.system = system; name = request.getParameter("name"); id = request.getParameter("id"); } public void execute() { Patron patron = new Patron(name, id); system.add(patron); } }
この記事は参考になりましたか?
- デザインパターンの使い方連載記事一覧
- この記事の著者
-
japan.internet.com(ジャパンインターネットコム)
japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
Jeff Langr(Jeff Langr)
本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle Bob Martin著、Prentice Hall、2008年8月)にも寄稿している。また、ソフトウェア開発に関する記事を80件以上執筆しており、そのうちの...
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
