Chapter5 Prototype
Chapter5 Prototype
Contents
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
5.5 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1
5.1 Introduction
In this lecture you will learn all about the Prototype design pattern. We discuss the
principles it applies, the problems it addresses, and some implementation issues.
This pattern applies the basic concept of caching. Caching is a general technique that
is applied in practice to improve the performance of a system at various levels ranging
from hardware implementation of cache memory up to its use in design and in application
programs. We explain the use of caching at the hand of a few practical examples on
program implementation and use level.
The implementation of the prototype pattern relies on the ability to copy existing objects.
Thus, when implementing this pattern the programmer should be aware of the practical
implications of shallow and deep copying of complex objects. We define and explain
these concepts to equip the reader with the required insight. We also discuss the idea
of a copy constructor. The C++ language automatically provides a default constructor,
copy constructor and destructor for each defined class. However, in some cases these
defaults does not behave as indented by the design. In these cases the programmer has
to provide an implementation to override the inappropriate default. This lecture gives
you the necessary background knowledge to be able to implement the prototype design
pattern.
5.2.1 Caching
The principle behind the application of the prototype pattern is the concept of caching. To
cache something on implementation level is to save the result of an expensive calculation
so that you don’t have to perform the calculation the next time its result is needed.
Sometimes caching results is more efficient than re-doing the calculations every time the
result is needed. The tradeoff is the the use of more memory for storage of the results.
2
The execution speed of this function can be drastically improved by precomputing values
and store the results in lookup tables. A lookup table for the top calculation can be
generated by the executing following code once:
int v a l u e [ 1 0 ] ;
for ( int i = 0 ; i < 1 0 ; i ++)
{
value [ i ] = i ∗ i − i ;
}
Similarly a lookup table for the values of sin can be created by executing the following
loop once:
int s i n T a b l e [ 3 6 0 ] ;
for ( int i =0; i <360; i ++)
{
sinTable [ i ] = sin ( i ) ;
}
After defining these lookup tables, the above function can then be altered to simply look up
these values instead of calculating them, resulting in marked performance improvement:
int nextX ( int x )
{
i f ( x < 1 0)
{
return v a l u e [ x ] ;
}
else
{
return s i n T a b l e [ x % 3 6 0 ] ;
}
}
Caching is often applied to dynamic web-pages. The page is cached when it is loaded the
first time. When it it needed subsequent times, instead of redoing all the calculations that
may involve database queries, expensive template rendering, and execution of business
logic, the cached page is simply copied from the cache.
Caching can often be used to speed up the execution of a spreadsheet containing expensive
computations that are re-used. For example the following formula requires that the same
time expensive formula needs to be calculated twice:
B1=IF(ISERROR(time_expensive_formula),0, time_expensive_formula)
3
The execution speed can be halved by storing (caching) the result of this formula and
simply re-using it by reference the next time:
A1=time_expensive_formula
B1=IF(ISERROR(A1),0,A1)
If an object of a class is copied, the copy constructor is called to construct the copy.
The default copy constructor will perform a shallow copy. This means that the values of
the instance variables of the original object are assigned to the corresponding instance
variables of the copied object. If these instance variables are primitives, this poses no
problem. However, if the instance variables are pointers to primitives or objects, assigning
their values to the pointer variables of the copied object would mean that these pointers
will point to the same objects. The result in a situation where the instance variables of
the copy of the object and the original object are shared is shown in Figure 1.
Original Object Copied Object
Figure 1: Copy of an object that shares the instance variables of the original
If the instance variables of a class are defined as pointers to dynamically allocated mem-
ory, it is better to define your own copy constructor that performs a deep copy. When
performing a deep copy, a duplicate object of each instance variable of the original object
is created. The result in a situation where the copy of the object has its own copies of
the instance variables of the original object as shown in Figure 2. This way the copied
object and the original object are totally independent.
4
Original Object Copied Object
Figure 2: Copy of an object that has its own copies of the instance variables of the original
In C++ every class has a copy constructor. It is a constructor that takes one parameter.
This parameter is an object of the class. If the programmer does not implement a copy
constructor, a default is provided. The default copy constructor makes a shallow copy.
When implementing the copy constructor one should use the default signature of the copy
constructor in order to override it. It takes a reference to a const parameter. It is const
to guarantee that the copy constructor doesn’t change it, and it is a reference because if
it was a value parameter the execution would require making a copy, which automatically
invokes the copy constructor resulting in infinite recursive calls to the copy constructor.
The following is an example of the definition of a Person class containing explicit defini-
tions of its default constructor, copy constructor, destructor and assignment operator.
c l a s s Person
{
public :
Person ( ) ; // d e f a u l t c o n s t r u c t o r
Person ( const Person & ) ; // copy c o n s t r u c t o r
Person & operator=(const Person & ) ; // a s s i g n m e n t o p e r a t o r
˜ Person ( ) ; // d e s t r u c t o r
private :
char∗ name ;
Address a d d r e s s ;
int age ;
};
Assume the class Address is properly defined and implemented. The following is an
example of the implementation of the copy constructor of this Person class:
Person : : Person ( const Person & p ) : age ( p . age ) , a d d r e s s ( p . a d d r e s s )
{
char∗ temp = new char [ s t r l e n ( p . name ) + 1 ] ;
s t r c p y ( temp , p . name ) ;
name = temp ;
}
5
Notice how it provides a deep copy of the character array containing the name of a
person. Also notice how it uses a member-list to intialise the age and address instance
variables. The first expression in this list (age(p.age)), initialises the instance variable
called age, to the value of the corresponding instance variable of the Person object p
that was passed as parameter to this copy constructor. Similarly, the second expression
in this list (address(p.address)), initialises the instance variable called address, to the
value of the corresponding instance variable of the Person object p that was passed as
parameter to this copy constructor. However, in this case this instance variable is not a
primitive data type. Therefore, the copy constructor of the Address class will be called
to construct a copy of the address contained in p.address.
When a copy constructor is called a new object is created that is a clone of the object that
was passed as a parameter. The following are examples of calls to the copy constructor
of a class Person, assuming that p is an existing Person object.
Person ∗ q = new Person ( p ) ;
Person r ( p ) ;
Person s = p ;
In the first statement the copy constructor is explicitly called to create a new Person q
that is a clone of p. The second statement implicitly calls copy constructor to build a
Person object r to be a clone of p. The last statement initialises the variable s where it
is declared. This statement also implicitly calls copy constructor to build a Person. In
this case the object s is created to be a clone of p. Similar to how the copy constructor
of Person calls the copy constructor of Address, the copy constructor of Person can be
called whenever it appears as value parameter that is initialised with an argument.
If your design requires shallow copies, there is no need to implement a copy constructor.
If the object has no pointers to dynamically allocated memory, there is no difference
between a shallow and a deep copy. Therefore the default copy constructor is sufficient
and you don’t need to write your own. However, if you implement a copy constructor, it
is good practice to also provide a destructor and an assignment operator.
5.2.2.4 Summary
Understanding the difference between shallow and deep copying enables you to apply
the appropriate copying when implementing a system. Visit http://www.youtube.com/
watch?v=xCq3D9aFAyI to see a video containing an explanation of the difference between
deep and shallow copying. Understanding this difference combined with an awareness
of the nature of the default copy constructor enables you to use or implement the most
suitable copy constructor for your current design.
6
5.3 Prototype Pattern
5.3.1 Identification
Name Classification Strategy
Prototype Creational Delegation
Intent
Specify the kinds of objects to create using a prototypical instance, and create new
objects by copying this prototype (Gamma et al. [2]:117)
5.3.2 Structure
5.3.3 Problem
The constructor of a class contains computationally expensive or manually time consuming
procedures. These can be avoided by creating a copy of the object rather than going
through the entire creation process each time from the beginning.
5.3.4 Participants
Prototype
ConcretePrototype
7
Client
ConcretePrototype
PrototypeManager
8
• Act as mediator through which the client can ask a selected prototype to clone
itself
Client
• Creates a new object by asking the prototype manager to ask a selected pro-
totype to clone itself
1. When it is known that the constructor of the class contains a computationally ex-
pensive or time consuming processes. In such cases processing time can be saved by
the application of the prototype design pattern. Copying an object would be faster
than creating a new object from scratch. A good example is when the constructor
does file I/O.
2. When it is known that a relative small set of standard variations of the objects
will be needed by users, the prototype design pattern can be applied to save user
effort. In stead of expecting the user to specify a number of fine grained detailed
information before an object is created, the user is presented with a set of prototypes
to select from in which each of the prototypes the finer grained information has a
certain combination of default values. Therefore, when instances of a class can have
one of only a few combinations of state, these combinations can be encapsulated in
prototype instances that can easily be recreated.
9
• The system structure will be more streamlined. Without the application of the pro-
totype pattern, the creation of a variety of object types is achieved by implementing
derived classes to instantiate the different types resulting in an elaborate hierarchy,
whereas the same variety can be achieved simply by creating prototypes instead of
classes.
• When instances of a class can have one of only a few different combinations of state.
It may be more convenient to install a corresponding number of prototypes and clone
them rather than instantiating the class manually, each time with the appropriate
state.
• Prototype reduces the necessity to subclass as what is done in the Factory Method.
The Creator hierarchy of Factory Method is reduced to one class and the duty of
creating a “copy” is left to the object itself.
• Generating a GUI having many numbers of similar controls: This is a quite frequent
scenario. One can have a form or GUI that hosts many similar controls. In order to
maintain the consistency, one needs to initialise every object to the same standard
state. This process of initialisation gets repeated again and again for each control
increasing the number of lines of code. In order to optimise this part of the code,
one can one have an object of a control initialised to a standard state and then
replicate the same object to create as many controls needed.
• Building a game that often reuse different visual objects: The Prototype pattern
is useful in this case because instead of creating the objects that get instantiated,
various objects can easily be created during the game execution by cloning proto-
typical objects. An added benefit is that changes to the scene an objects in a scene
can rapidly be changed by replacing the prototypical objects with different ones.
• Applying a variety of analyses on the same result set from a database: Database
queries and the creation of result sets are computationally expensive operations.
When an application does an analysis on a set of data from a database, normally
the information from the database is encapsulated into an object and the analysis
is performed on the object. If more analyses are needed on the same set of data,
reading the database again and creating a new object for each analysis is expensive
and can be avoided by using the Prototype pattern. The object used in the first
analysis can be cloned and used for subsequent analyses.
10
5.4.4 Implementation Issues
The hardest part of the Prototype pattern is implementing the clone() operation cor-
rectly. In C++ an elegant way would be to use the copy constructor on *self. However,
this is dependent on the correct implementation of the copy constructor. When cloning
prototypes with complex structures, it is important to make deep copies to allow the copy
to exist independent of the prototype [2].
In cases where there is a need to create variations of clones, one can parameterise the
clone() operation. However, passing parameters in the clone() operation precludes a
uniform cloning interface. One can also implement additional initialisation operations.
However, in these situations the programmer should beware of deep-copying operations
as the copies may have to be deleted (either explicitly or within Initialise) before you
reinitialize them.
• The cloning of objects by an application program does necessary imply that the
prototype pattern is applied. An example that has been mentioned as a possible
scenario for the application of the prototype pattern cloning the sessions from one
server to another without disturbing the clients in an enterprise level application
managing a server pool. It is unlikely that this would be implemented using the
prototype design pattern since the reason for cloning these objects are not according
to the intent of the pattern to clone multiple instances of an object that is hard to
create from scratch. This scenario rather justify the application of the memento
design pattern.
Abstract Factory
Prototype and Abstract Factory are competing patterns in some ways. Prototype
define new types simply by creating new prototypes while Abstract Factory requires
the creation of new classes for defining new types. However, they can also be used
together. An Abstract Factory might store a set of prototypes from which to clone
and return product objects.
11
Abstract Factory and Builder
Similar to the prototype design pattern, these patterns hides the concrete product
classes from the client, thereby reducing the number of names clients know about.
Composite and Decorator
Designs that make heavy use of the Composite and Decorator patterns often can
benefit from Prototype.
Singleton and Abstract Factory Prototype, Singleton and Abstract Factory are all
creational patterns where you don’t use the new keyword to create a product but
you call a method that will create the specific product and return a pointer to it.
Singleton, Memento and Flyweight
These patterns administrate access to specific object instances similar to how Pro-
totype administrates it. All of them offer factory methods to clients and share a
create-on-demand strategy [4].
5.5 Example
Figure 5 is a class diagram of an application that implements the prototype design pattern.
It is a nonsense program that allows the user to create a variety of objects. It also allows
the user to nominate existing objects as prototypes which can be cloned. The instance
variables of the objects that are cloned in this example are primitive data types. Therefore,
there is no need to implement deep copies in their copy constructors or when cloning the
objects. In this regard it is not a good example. However, the objects have time consuming
constructors, which make them ideal candidates to benefit from being cloned rather than
created from scratch when needed.
12
Participant Entity in application
Prototype RealValue
Concrete Prototypes PlainRealValue, ArrayRealValue, FileRealValue
Prototype Manager Manager
Client main()
Prototype
ConcretePrototype
Prototype Manager
• The Manager class uses a map for keeping prototypes and provide functionality
to add and remove prototypes.
• It acts as mediator through which clients can add and remove prototypes form
the store and can also gain access to any of the stored prototypes.
• in this application the prototype store maintains pointers to objects owned by
the client.
Client
13
• In this application the client has a Manager called manager. The user can
specify at runtime which of the existing objects should be registered and used
as prototypes, and uses manager to keep track of them.
• The client has a vector called object, of objects which are created on demand.
The user can choose to create objects from scratch or to clone objects that has
been registered as prototypes.
• When the user has chosen the appropriate option to clone an object, a new
object is created by asking manager to ask a selected prototype to clone itself.
In this example the user type the name of a prototype which is read into a
variable named name. Thereafter a clone of the identified prototype is added
to a vector of objects called object by executing the following statement:
o b j e c t . push back ( manager . g e t R e a l V a l u e ( name)−> c l o n e ( ) ) ;
5.6 Exercises
1. See http://www.codeproject.com/KB/architecture/Prototype_Design_Pattern.
aspx for another nice example of an implementation of the prototype design pattern.
2. Write a simple system that manage presentations offered by Social Informer Pty
Ltd (S-Inf). Identify attributes of each presentation should have – length, price,
topic, etc. – and set them up as prototypes managed by a manager class. Create
a test program that allows the administrator of S-Inf to select a presentation, get
a copy of it, and fill in detail – date, venue, presenter, capacity, etc. – to create an
instance for which members of public can register to attend.
References
[1] Judith Bishop. C# 3.0 design patterns. O’Reilly, Farnham, 2008.
[2] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns :
elements of reusable object-oriented software. Addison-Wesley, Reading, Mass, 1995.
[4] Jan Hannemann and Gregor Kiczales. Design pattern implementation in java and
aspectj. SIGPLAN Notes, 37:161–173, November 2002. ISSN 0362-1340.
14