In this article by Kyle Mew, author of the book Android Design Patterns and Best Practices, we will discuss how the prototype design pattern performs similar tasks to other creational patterns, such as builders and factories, but it takes a very different approach. Rather than relying heavily on a number of hardcoded subclasses, the prototype, as its name suggests, makes copies of an original, vastly reducing the number of subclasses required and preventing any lengthy creation processes.
(For more resources related to this topic, see here.)
The prototype is most useful when the creation of an instance is expensive in some way. This could be the loading of a large file, a detailed cross-examination of a database, or some other computationally expensive operation. Furthermore, it allows us to decouple cloned objects from their originals, allowing us to make modifications without having to reinstantiate each time. In the following example, we will demonstrate this using functions that take considerable time to calculate when first created, the nth prime number and the nth Fibonacci number.
Viewed diagrammatically, our prototype will look like this:
We will not need the prototype pattern in our main app as there are very few expensive creations as such. However, it is vitally important in many situations and should not be neglected. Follow the given steps to apply a prototype pattern:
public abstract class Sequence implements Cloneable {
protected long result;
private String id;
public long getResult() {
return result;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
// Calculates the 10,000th prime number
public class Prime extends Sequence {
public Prime() {
result = nthPrime(10000);
}
public static int nthPrime(int n) {
int i, count;
for (i = 2, count = 0; count < n; ++i) {
if (isPrime(i)) {
++count;
}
}
return i - 1;
}
// Test for prime number
private static boolean isPrime(int n) {
for (int i = 2; i < n; ++i) {
if (n % i == 0) {
return false;
}
}
return true;
}
}
// Calculates the 100th Fibonacci number
public class Fibonacci extends Sequence {
public Fibonacci() {
result = nthFib(100);
}
private static long nthFib(int n) {
long f = 0;
long g = 1;
for (int i = 1; i <= n; i++) {
f = f + g;
g = f - g;
}
return f;
}
}
public class SequenceCache {
private static Hashtable<String, Sequence> sequenceHashtable = new Hashtable<String, Sequence>();
public static Sequence getSequence(String sequenceId) {
Sequence cachedSequence = sequenceHashtable.get(sequenceId);
return (Sequence) cachedSequence.clone();
}
public static void loadCache() {
Prime prime = new Prime();
prime.setId("1");
sequenceHashtable.put(prime.getId(), prime);
Fibonacci fib = new Fibonacci();
fib.setId("2");
sequenceHashtable.put(fib.getId(), fib);
}
}
// Load the cache once only
SequenceCache.loadCache();
// Lengthy calculation and display of prime result
Sequence prime = (Sequence) SequenceCache.getSequence("1");
primeText.setText(new StringBuilder()
.append(getString(R.string.prime_text))
.append(prime.getResult())
.toString());
// Lengthy calculation and display of Fibonacci result
SSequence fib = (Sequence) SequenceCache.getSequence("2");
fibText.setText(new StringBuilder()
.append(getString(R.string.fib_text))
.append(fib.getResult())
.toString());
As you can see, the preceding code creates the pattern but does not demonstrate it. Once loaded, the cache can create instant copies of our previously expensive output. Furthermore, we can modify the copy, making the prototype very useful when we have a complex object and want to modify just one or two properties.
Consider a detailed user profile similar to what you might find on a social media site. Users modify details such as images and text, but the overall structure is the same for all profiles, making it an ideal candidate for a prototype pattern.
To put this principle into practice, include the following code in your client source code:
// Create a clone of already constructed object
Sequence clone = (Fibonacci) new Fibonacci().clone();
// Modify the result
long result = clone.getResult() / 2;
// Display the result quickly
cloneText.setText(new StringBuilder()
.append(getString(R.string.clone_text))
.append(result)
.toString());
The prototype is a very useful pattern in many occasions, where we have expensive objects to create or when we face a proliferation of subclasses. Although this is not the only pattern that helps reduce excessive sub classing, it leads us on to another design pattern, decorator.
In this article we explored the vital pattern, the prototype pattern and saw how vital it can be whenever we have large files or slow processes to contend with.
Further resources on this subject: