I am studying design patterns and I've implemented a composite prototype pattern which is also a singleton (one instance per JVM). The main and most important part of the code is the Registry class
which is a singleton.
The Movie
and Book
classes are inherited from the abstract Item
class which implements the Cloneable
interface of Java. I haven't changed the default cloneable()
method to make a shallow copy of the Item
objects whenever they are cloned. I'm not sure If it is better to choose a deep copy implementation in this simple example.
The responsibility of the Registry class is:
- Store the items prototypes in a map
- Create items with cloning the stored prototypes
Item
class
package com.levent.prototype;
public abstract class Item implements Cloneable {
// fields
private long itemId;
private String itemTitle;
private ItemStatus itemStatus;
// inner static enum
public static enum ItemStatus {
AVAILABLE,
SOLD
}
// argument constructor
public Item(long itemId, String itemTitle, ItemStatus itemStatus) {
this.itemId = itemId;
this.itemTitle = itemTitle;
this.itemStatus = itemStatus;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// returns a shallow copy of the Item or subclass objectss
// Should implement a deep copy instead ?
return super.clone();
}
// getters & setters
public long getItemId() {
return itemId;
}
public void setItemId(long itemId) {
this.itemId = itemId;
}
public String getItemTitle() {
return itemTitle;
}
public void setItemTitle(String itemTitle) {
this.itemTitle = itemTitle;
}
public ItemStatus getItemStatus() {
return itemStatus;
}
public void setItemStatus(ItemStatus itemStatus) {
this.itemStatus = itemStatus;
}
}
Movie
class
package com.levent.prototype;
public class Movie extends Item {
// fields
private int duration;
private String director;
// argument constructor
public Movie(long itemId, String itemTitle, ItemStatus itemStatus, int duration, String director) {
super(itemId, itemTitle, itemStatus);
this.duration = duration;
this.director = director;
}
// getters & setters
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public String getDirector() {
return director;
}
public void setDirector(String director) {
this.director = director;
}
}
Book
class
package com.levent.prototype;
public class Book extends Item {
// fields
private int numberOfPages;
private String author;
// argument constructor
public Book(long itemId, String itemTitle, ItemStatus itemStatus, int numberOfPages, String author) {
super(itemId, itemTitle, itemStatus);
this.numberOfPages = numberOfPages;
this.author = author;
}
// getters & setters
public int getNumberOfPages() {
return numberOfPages;
}
public void setNumberOfPages(int numberOfPages) {
this.numberOfPages = numberOfPages;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Registry
class
package com.levent.prototype;
import java.util.HashMap;
import java.util.Map;
public class Registry {
public static enum TYPE {
MOVIE,
BOOK
}
private static Registry instance = null; // lazy loading
private Map<TYPE, Item> objectMap = null;
private Registry() {
// using synchronized only for once so
// there will be no performance issues
// regarding usage of synchronized
// but not sure if necessity is a MUST ?
synchronized(Registry.class) {
objectMap = new HashMap<TYPE, Item>();
loadItemPrototypes();
}
}
public static Registry getInstance() {
if(instance == null) {
// using synchronized only if instance is null
synchronized(Registry.class) {
if(instance == null) {
instance = new Registry();
}
}
}
return instance;
}
// load object prototypes to map
private void loadItemPrototypes() {
Movie movie = new Movie(1, "Default Movie", Item.ItemStatus.AVAILABLE, 86, "John Doe" );
objectMap.put(TYPE.MOVIE, movie);
Book book = new Book(1, "Default Book", Item.ItemStatus.AVAILABLE, 342, "J. Random Hacker" );
objectMap.put(TYPE.BOOK, book);
}
// fetches the type from prototype map and returns a clone
// of the item prototype
public Item createItem(TYPE type) {
Item item = null;
try {
item = (Item)(objectMap.get(type)).clone();
} catch (CloneNotSupportedException e) {
System.out.println("Registry::createItem()::Error on type: " + type);
e.printStackTrace();
}
return item;
}
}
Demo
code
package com.levent.prototype;
public class SingletonPrototypeDemo {
static final int ITEM_SIZE = 6;
public static void main(String[] args) {
Registry registry = Registry.getInstance(); // init
Item items[] = new Item[ITEM_SIZE];
initItems(registry, items);
// Assign some valus to the clones
// Movies
((Movie) items[1]).setDirector("Adam K. Foo");
((Movie) items[2]).setDirector("Zac Mac Kracker");
((Movie) items[1]).setDuration(92);
((Movie) items[2]).setDuration(66);
items[1].setItemStatus(Item.ItemStatus.SOLD);
items[2].setItemStatus(Item.ItemStatus.SOLD);
items[1].setItemTitle("The Story Of A Man Who Lives Silly");
items[2].setItemTitle("My Teacher And Her Stupid Green Martian Lover");
items[1].setItemId(2);
items[2].setItemId(3);
// Books
((Book) items[4]).setAuthor("Michael Z. Dude");
((Book) items[5]).setAuthor("Mr. Anonymous");
((Book) items[4]).setNumberOfPages(35);
((Book) items[5]).setNumberOfPages(983);
items[4].setItemStatus(Item.ItemStatus.SOLD);
items[5].setItemStatus(Item.ItemStatus.SOLD);
items[4].setItemTitle("Dudeism : The New Religion");
items[5].setItemTitle("A Guide To Everything That You Don't Need To Know");
items[3].setItemId(4);
items[4].setItemId(5);
items[5].setItemId(6);
// print
handlePrint(items);
}
private static void printItem(Item item) {
System.out.println("Object : " + item);
System.out.println("Item Id : " + item.getItemId());
System.out.println("Item Title : " + item.getItemTitle());
System.out.println("Item Status : " + item.getItemStatus());
}
private static void initItems(Registry registry, Item items[]) {
// half of items are movie and the rest are book
for(int i = 0; i < ITEM_SIZE; i++) {
if(i < ITEM_SIZE/2)
items[i] = (Movie) registry.createItem(Registry.TYPE.MOVIE);
else
items[i] = (Book) registry.createItem(Registry.TYPE.BOOK);
}
}
private static void handlePrint(Item items[]) {
// half of items are movie and the rest are book
for(int i = 0; i < ITEM_SIZE; i++) {
if(i < ITEM_SIZE/2)
printMovie((Movie)items[i]);
else
printBook((Book)items[i]);
System.out.println();
}
}
private static void printBook(Book book) {
printItem(book);
System.out.println("Book Author : " + book.getAuthor() );
System.out.println("Book PageNum : " + book.getNumberOfPages() );
}
private static void printMovie(Movie movie) {
printItem(movie);
System.out.println("Movie Director : " + movie.getDirector());
System.out.println("Movie Duration : " + movie.getDuration());
}
}
Sample output of demo code
Object : com.levent.prototype.Movie@6d06d69c Item Id : 1 Item Title : Default Movie Item Status : AVAILABLE Movie Director : John Doe Movie Duration : 86 Object : com.levent.prototype.Movie@7852e922 Item Id : 2 Item Title : The Story Of A Man Who Lives Silly Item Status : SOLD Movie Director : Adam K. Foo Movie Duration : 92 Object : com.levent.prototype.Movie@4e25154f Item Id : 3 Item Title : My Teacher And Her Stupid Green Martian Lover Item Status : SOLD Movie Director : Zac Mac Kracker Movie Duration : 66 Object : com.levent.prototype.Book@70dea4e Item Id : 4 Item Title : Default Book Item Status : AVAILABLE Book Author : J. Random Hacker Book PageNum : 342 Object : com.levent.prototype.Book@5c647e05 Item Id : 5 Item Title : Dudeism : The New Religion Item Status : SOLD Book Author : Michael Z. Dude Book PageNum : 35 Object : com.levent.prototype.Book@33909752 Item Id : 6 Item Title : A Guide To Everything That You Don't Need To Know Item Status : SOLD Book Author : Mr. Anonymous Book PageNum : 983
The most of the work is, as I've written above is made on Registry
class. I've written this whole code to make a demo of a prototype pattern and which is also a singleton pattern. The Registry
class has a composite structure, is a singleton-prototype.
It is singleton because it has only one instance per JVM. It is also a prototype because it stores object prototypes and creates by cloning them (and notice that base Item class implements the Cloneable
interface).