Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we're going to learn about one of the Creational Design Patterns – the Prototype pattern. At first, we'll explain this pattern and then proceed to implement it in Java.

We'll also discuss some of its advantages and disadvantages.

2. Prototype Pattern

The Prototype pattern is generally used when we have an instance of the class (prototype) and we'd like to create new objects by just copying the prototype.

Let's use an analogy to better understand this pattern.

In some games, we want trees or buildings in the background. We may realize that we don't have to create new trees or buildings and render them on the screen every time the character moves.

So, we create an instance of the tree first. Then we can create as many trees as we want from this instance (prototype) and update their positions. We may also choose to change the color of the trees for a new level in the game.

The Prototype pattern is quite similar. Instead of creating new objects, we just have to clone the prototypical instance.

3. UML Diagram

In the diagram, we see that the client is telling the prototype to clone itself and create an object. Prototype is an interface and declares a method for cloning itself. ConcretePrototype1 and ConcretePrototype2 implement the operation to clone themselves.

4. Implementation

One of the ways we can implement this pattern in Java is by using the clone() method. To do this, we'd implement the Cloneable interface.

When we're trying to clone, we should decide between making a shallow or a deep copy. Eventually, it boils down to the requirements.

For example, if the class contains only primitive and immutable fields, we may use a shallow copy.

If it contains references to mutable fields, we should go for a deep copy. We might do that with copy constructors or serialization and deserialization.

Let's take the example we mentioned earlier and proceed to see how to apply the Prototype pattern without using the Cloneable interface. In order to do this, let's create an abstract class called Tree with an abstract method ‘copy'.

public abstract class Tree {
    
    // ...
    public abstract Tree copy();
    
}

Now let's say we have two different implementations of Tree called PlasticTree and PineTree:

public class PlasticTree extends Tree {

    // ...

    @Override
    public Tree copy() {
        PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());
        plasticTreeClone.setPosition(this.getPosition());
        return plasticTreeClone;
    }

}
public class PineTree extends Tree {
    // ...

    @Override
    public Tree copy() {
        PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());
        pineTreeClone.setPosition(this.getPosition());
        return pineTreeClone;
    }
}

So here we see that the classes which extend Tree and implement the copy method can act as prototypes for creating a copy of themselves.

Prototype pattern also lets us create copies of objects without depending on the concrete classes. Let's say we have a list of trees and we would like to create copies of them. Due to polymorphism, we can easily create multiple copies without knowing the types of trees.

5. Testing

Now let's test it:

public class TreePrototypesUnitTest {

    @Test
    public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() {
        // ...

        PlasticTree plasticTree = new PlasticTree(mass, height);
        plasticTree.setPosition(position);
        PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy();
        anotherPlasticTree.setPosition(otherPosition);

        assertEquals(position, plasticTree.getPosition());
        assertEquals(otherPosition, anotherPlasticTree.getPosition());
    }
}

We see that the tree has been cloned from the prototype and we have two different instances of PlasticTree. We've just updated the position in the clone and retained the other values.

Now let's clone a list of trees:

@Test
public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() {

    // create instances of PlasticTree and PineTree

    List<Tree> trees = Arrays.asList(plasticTree, pineTree);
    List<Tree> treeClones = trees.stream().map(Tree::copy).collect(toList());

    // ...

    assertEquals(height, plasticTreeClone.getHeight());
    assertEquals(position, plasticTreeClone.getPosition());
}

Notice that we are able to do a deep copy of the list here without being dependent on the concrete implementations of Tree.

6. Advantages & Disadvantages

This pattern is handy when our new object is only slightly different from our existing one. In some cases, instances may have only a few combinations of state in a class. So instead of creating new instances, we may create the instances with the appropriate state beforehand and then clone them whenever we want.

Sometimes, we might encounter subclasses that differ only in their state. We can eliminate those subclasses by creating prototypes with the initial state and then cloning them.

Prototype pattern, just like every other design pattern, should be used only when it's appropriate. Since we are cloning the objects, the process could get complex when there are many classes, thereby resulting in a mess. Additionally, it's difficult to clone classes that have circular references.

7. Conclusion

In this tutorial, we learned the key concepts of the Prototype pattern and saw how to implement it in Java. We also discussed some of its pros and cons.

As usual, the source code for this article is available over on Github.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
newest oldest most voted
Notify of
Alex
Guest
Alex

Usually the examples are good. But this could have been inserted into a specific context where the pattern is usefull. The interface shown in the UML is absent so the reader does not even understand that the pattern takes advantage of virtual methods to avoid writting too much code when another implementation is needed by the calling code. However each time you call clone then a new object is created in the heap so your code is not going to minimize the use of memory and processor time neither. At last you are not going to use this pattern if… Read more »

Eric Martin
Member
Eric Martin

Hey Alex,
Thanks for your observation. We’ll update the article soon.
Cheers.

Comments are closed on this article!