Prototypeパターンの例
図書館の職員が図書館システムにたくさんの新しい本と映画ソフトを追加しているところを想像してみてください。職員は、スキャナを使って本に付けられたバーコードを読み取ります(話を簡単にするために、バーコードは本の分類番号と同じであるとしておきましょう)。
その本が、図書館に追加される最初の1冊である場合、システムは外部API呼び出しを行って、本の著者(映画の場合は監督)、タイトル、出版年などの基本情報を確認します。この検索にはとても時間がかかります。さらに、職員は返ってきた情報を目で見て確認し、誤りがあれば修正しなければなりません。
リスト1は、このような要件に基づいた所蔵目録(カタログ)の実装を示しています。このテストは、本や映画のオブジェクトを作成し、これらのオブジェクトに適切なデータを設定するという作業をクライアントコードが担当することを示しています。ここでは、このクライアントから外部API呼び出しを行うと仮定しましょう。資料オブジェクトが作成されると、クライアントコードはaddNew
を呼び出してそのオブジェクトを所蔵目録に追加します。
// 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冊目の本を検索し、その情報を使用してコピーを作成する処理は、所蔵目録が内部的に行います。
@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クラスも影響を受けます。
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; }''