Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

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

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

 Prototypeパターンは、優れたオブジェクト指向デザインの持つその他の基本的な考え方にのっとって、ポリモーフィックであり、依存性逆転の原則と単一責務の原則に合致しています。本稿では、図書館などで使われる所蔵目録(カタログ)システムの実装を例に取り、解説していきます。

目次

Prototypeパターンの例

 図書館の職員が図書館システムにたくさんの新しい本と映画ソフトを追加しているところを想像してみてください。職員は、スキャナを使って本に付けられたバーコードを読み取ります(話を簡単にするために、バーコードは本の分類番号と同じであるとしておきましょう)。

 その本が、図書館に追加される最初の1冊である場合、システムは外部API呼び出しを行って、本の著者(映画の場合は監督)、タイトル、出版年などの基本情報を確認します。この検索にはとても時間がかかります。さらに、職員は返ってきた情報を目で見て確認し、誤りがあれば修正しなければなりません。

 リスト1は、このような要件に基づいた所蔵目録(カタログ)の実装を示しています。このテストは、本や映画のオブジェクトを作成し、これらのオブジェクトに適切なデータを設定するという作業をクライアントコードが担当することを示しています。ここでは、このクライアントから外部API呼び出しを行うと仮定しましょう。資料オブジェクトが作成されると、クライアントコードはaddNewを呼び出してそのオブジェクトを所蔵目録に追加します。

リスト1 単純な所蔵目録
// CatalogTest.java
import static org.junit.Assert.*;

import java.util.*;
import org.junit.*;

public class CatalogTest {
   private Catalog catalog;

   @Before
   public void initialize() {
      catalog = new Catalog();
   }

   @Test
   public void isEmptyOnCreation() {
      assertEquals(0, catalog.size());
   }

   @Test
   public void addNewBook() {
      final Book book = new Book("QA123", "author", "title",
                                 "1999", 1);
      catalog.addNew(book);
      assertEquals(1, catalog.size());
      List<Material> materials = catalog.get("QA123");
      assertEquals(1, materials.size());
      assertSame(book, materials.get(0));
   }
}

// Catalog.java
import java.util.*;

public class Catalog {
   private MultiMap<String, Material> materials =
      new MultiMap<String, Material>();

   public int size() {
      return materials.valuesSize();
   }

   public void addNew(Material material) {
      materials.put(material.getClassification(), material);
   }

   public List<Material> get(String classification) {
      return materials.get(classification);
   }
}

 追加される資料の多くは重複しており、重複している資料の場合、登録すべき情報はすべて同じです(そのため、このような資料については情報を確認する必要はないだろうと皆さんなら考えますね)。この所蔵目録では、既に存在している資料の複製(つまりプロトタイプ)を作成し、API呼び出しを回避するようにします。

 リスト2のテストのシナリオはこうです。まず1冊目の本の情報が外部で作成され、所蔵目録に追加されます。続いて2冊目の本がスキャンされます。スキャンされた分類は、既にシステムに存在するものであるため、クライアントは分類だけを渡してaddCopyを呼び出します。1冊目の本を検索し、その情報を使用してコピーを作成する処理は、所蔵目録が内部的に行います。

リスト2 重複する本を追加するテスト
@Test
public void addCopyViaPrototype() {
   final Book book = new Book("QA123", "title", "author",
                              "1999", 1);
   catalog.addNew(book);

   catalog.addCopy("QA123");

   assertEquals(2, catalog.size());

   List<Material> materials = catalog.get("QA123");
   assertEquals(2, materials.size());
   assertSame(book, materials.get(0));

   Material copy = materials.get(1);
   assertTrue(copy instanceof Book);
   assertEquals("QA123", copy.getClassification());
   assertEquals("author", copy.getAuthor());
   assertEquals("title", copy.getTitle());
   assertEquals("1999", copy.getYear());
   assertEquals(2, copy.getCopyNumber());
}

 リスト3は、これらのテストをパスする程度には整った、簡単な実装を示しています。ただし、これはあまり良い実装とはいえません。冊数を取得するアルゴリズムには問題があるかもしれませんが、ここでは気にしないでください。本当に問題なのは、copyメソッドが不恰好なものになるだろうということです。このテストを本だけでなく映画にも適用しようと思うと、copyメソッドには資料のタイプを確認するためのif文が必要になります。さらに、さまざまなオブジェクトタイプの構造を詳細に記述していけば、コードは煩雑になっていくでしょう。BookオブジェクトやMovieオブジェクトの構造が変更されれば、Catalogクラスも影響を受けます。

リスト3 リスト2のテストをパスする実装
public void addCopy(String classification) {
   List<Material> copies = materials.get(classification);
   ''materials.put(classification, copy(copies.get(0)));''
}

''private Material copy(Material material) {
   Book book = new Book(material.getClassification(),
         material.getTitle(),
         material.getAuthor(),
         material.getYear(),
         material.getCopyNumber() + 1);
   return book;
}''

  • 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-2018 Shoeisha Co., Ltd. All rights reserved. ver.1.5