3
\$\begingroup\$

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:

  1. Store the items prototypes in a map
  2. 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).

\$\endgroup\$
1
\$\begingroup\$

Cloneable

Some say Cloneable is broken in Java, advocating the use of copy-constructors instead. Perhaps you want to take a look in that direction.

I'm also not sure why you want to clone() from the two-element Map (which can be implemented as an EnumMap) to obtain your array objects.

Singletoness

As mentioned in an earlier answer to another question of yours, the recommended way is to to use the enum singleton pattern. If you do not want to do so for some reason (e.g. your singleton class must extend from an abstract class), make the singleton class final will prevent users from extending your class and then creating multiple instances of them.

Displaying human-readable output

That's what Object.toString() is for, it's recommended that you override that instead of having your printBook(Book)/printMovie(Movie) methods.

\$\endgroup\$
  • 1
    \$\begingroup\$ This answer seems very useful (I've also just check your other answer on ConnectionPool code) I've used clone because I want to update my code in future, which will take an unlimited set of Objects and read the type (class name) at runtime. Thank your very much for your useful answer. \$\endgroup\$ – Levent Divilioglu May 5 '16 at 7:34
  • \$\begingroup\$ I try to learn the best practice of Java language but because I study design patterns, your comments about the Cloneable interface is important. I've just read Joshua Bloch's view via the link you've provided and because that Java API's Cloneable's clone method is protected, I'll implement the same code/functionality on C++ with a copy constructor or whatever I can do with the language. It's very interesting that even Doug Lea give up on the Cloneable interface. It seems core Java API is written in god-speed and they've missed a very important point. \$\endgroup\$ – Levent Divilioglu May 5 '16 at 9:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.