Prototypeパターンでは、所蔵目録がプロトタイプオブジェクト(この場合はBookオブジェクト)に自分自身のクローンを返すよう要求するだけにします。つまり、すべての処理を行うのはBookクラスだということです。また、Movieタイプを追加することを考えると、このソリューションはポリモーフィックにすべきです。
このプロトタイプの考え方を取り入れて、より堅牢にリファクタリングしたソリューションをリスト4(テストコード)とリスト5(本番用コード)に示します。リスト6は、Material、Book、Movieの各クラスを示しています。
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));
}
@Test(expected=NoExistingCopyException.class)
public void addNewCopyThrowsIfNoExistingCopy() {
catalog.addCopy("QA123.4");
}
@Test
public void addCopyViaPrototype() {
final Book book = new Book("QA123", "title", "author",
"1999", 1);
catalog.addNew(book);
catalog.addCopy("QA123");
assertEquals(2, catalog.size());
assertMaterialCopies("QA123", 2, book);
}
@Test
public void addMovieCopyViaPrototype() {
final Movie movie = new Movie("DD890", "Shining", "Kubrick",
"2006", 1, Movie.Format.DVD);
catalog.addNew(movie);
catalog.addCopy("DD890");
assertEquals(2, catalog.size());
assertMovieCopies("DD890", 2, movie);
}
private void assertMaterialCopies(String classification,
int number, Material material) {
List<Material> materials = catalog.get(classification);
assertEquals(number, materials.size());
for (int i = 0; i < number; i++) {
Material copy = materials.get(i);
assertEquals(material.getClass(), copy.getClass());
assertEquals(material.getClassification(),
copy.getClassification());
assertEquals(material.getAuthor(), copy.getAuthor());
assertEquals(material.getTitle(), copy.getTitle());
assertEquals(material.getYear(), copy.getYear());
assertEquals(i + 1, copy.getCopyNumber());
}
}
private void assertMovieCopies(String classification,
int number, Movie material) {
assertMaterialCopies(classification, number, material);
for (Material copy: catalog.get(classification))
assertEquals(material.getFormat(),
((Movie)copy).getFormat());
}
}
import java.util.*;
public class Catalog {
private MultiMap<String, Material> materials =
new MultiMap<String, Material>();
public void addCopy(String classification) {
List<Material> copies = materials.get(classification);
if (copies.isEmpty())
throw new NoExistingCopyException();
''materials.put(classification, copies.get(0).copy());''
}
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);
}
}
// Material.java
abstract public class Material {
private final String title;
private final String classification;
private final String author;
private final String year;
private String branch;
private int copyNumber;
public Material(String classification, String title,
String author, String year, int copyNumber) {
this.classification = classification;
this.title = title;
this.author = author;
this.year = year;
this.copyNumber = copyNumber;
branch = "checked out";
}
abstract public Material copy();
public String getAuthor() {
return author;
}
public String getClassification() {
return classification;
}
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public int getCopyNumber() {
return copyNumber;
}
public String getBranch() {
return branch;
}
}
// Book.java
public class Book extends Material {
public Book(String classification, String title,
String author, String year,
int copyNumber) {
super(classification, title, author, year, copyNumber);
}
public Book copy() {
return new Book(getClassification(), getTitle(),
getAuthor(), getYear(),
getCopyNumber() + 1);
}
}
// Movie.java
public class Movie extends Material {
public static enum Format {
DVD, BluRay, VHS
}
private Format format;
public Movie(String classification, String title, String author,
String year, int copyNumber, Format format) {
super(classification, title, author, year, copyNumber);
this.format = format;
}
public Format getFormat() {
return format;
}
public Movie copy() {
return new Movie(getClassification(), getTitle(),
getAuthor(), getYear(),
getCopyNumber() + 1, getFormat());
}
}
この実装では、Javaのcloneメソッドを使用していません。これは、現在のコンストラクションデザインに基づいて私が行ったデザイン上の選択です。cloneを実装する場合は、シャローコピーかディープコピーかの検討も含めて、このメソッドを使用することによる影響をすべて考慮に入れるようにしてください。このトピックについてはJoshua Bloch著の『Effective Java』が参考になるでしょう。
Prototypeパターンの最も重要な点は、既存の資料に自分自身のコピーを返すように指示することで、コードではCatalogのaddCopyメソッドの太字で示した行(リスト5を参照)にあたります。この短いたった1行によって、Catalogは、あらゆる既存のMaterialまたはMaterialサブクラスの変更や、新しいMaterialサブクラスの追加などの影響を受けない、「自己完結」した存在になります。また、Prototypeパターンは、優れたオブジェクト指向デザインの持つその他の基本的な考え方にのっとって、ポリモーフィックであり、依存性逆転の原則と単一責務の原則に合致しています。

