SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

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

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

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

テスト駆動型のアプローチに基づいた、ソースコードのリファクタリングを題材にして、非常に便利で有名なデザインパターン「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);
   }
}

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
デザインパターンの使い方連載記事一覧

もっと読む

この記事の著者

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

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.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件以上執筆しており、そのうちの...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2974 2008/09/12 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング