Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

デザインパターンの使い方: Command

Commandパターンを利用したソースコードのリファクタリング

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/09/12 14:00

テスト駆動型のアプローチに基づいた、ソースコードのリファクタリングを題材にして、非常に便利で有名なデザインパターン「Commandパターン」を紹介します。

目次

Commandパターンの例

 皆さんのシステムには下記のリスト1に示すserviceのようなメソッドがありますか? このようなメソッドを、私は多くのシステムで目にしてきました。なぜこうなるのかは知りませんが、しばしばこういったメソッドのコードを変更したり、新しい種類のリクエストのサポートを追加したりする羽目になります。変更する際にはコードを壊さないように、メソッドの動作を単体テストで確認するようにしています。

 しかし、リスト1のようなコードのテストを作成するには一体どこから手をつけたらよいでしょうか。

リスト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引数を使って呼び出されることを確認します。最終的には、この方法でテストすることが最適とはおそらく言えないでしょうが、とりあえずはこれで良しとします。

リスト2 最初の単体テスト
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が互いに相手の存在を認識するという密結合の関係になってしまいました。これは望ましい状況ではないので、コードをチェックインするまでに何とか解決しようと思います。

リスト3 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);
   }
}

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

  • Jeff Langr(Jeff Langr)

    本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle...

バックナンバー

連載:デザインパターンの使い方

もっと読む

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5