Back in the old days when printing wasn't common, people used to use some sort of wooden stamps (later they became rubber stamps) to print some commonly used graphics and text on paper. Many years later, people realized that by combining different commonly used stamps, it could be one of the easiest ways to mass reproduce the same information on paper. Without using the same stamps for printing the same glyphs on paper, distribution of information and knowledge would be much more expensive and time-consuming.
In many object-oriented applications, there are objects that are just too expensive or complex to create. It would be a lot easier if we could recreate the same object with slight changes only. A typical example could be cloning a composite structure (e.g., a tree structure). It's very difficult to construct another tree composite from scratch. Rather we reuse the one that is available with slight modification to fit a particular situation in the program.
If we can turn some objects into rubber stamps and tell them to create a clone of themselves, it can save a lot of effort to build them. It's highly reusable and more maintainable than creating different individual classes with very few differences from the parent class.
The pattern that applies to the "clone" operation is called the Prototype pattern. Cloning is to manufacture a line of products with the same mold. An item that a mold is based on is a prototype. A prototype determines what a final product should be. Some attributes, like color and dimensions, could be slightly different despite the fact that the products are copied from the same mold. Despite the small differences, they are still of the same kind.
In this chapter, we are going to discuss some issues related to object cloning in general and then how to implement the Prototype pattern for cloning complex objects in Objective-C.
The Prototype pattern is one of the simplest design patterns. A client knows an abstract Prototype
class. At runtime any object that is a subclass of the abstract Prototype
can be cloned at the client's will. So the client can make multiple instances of the same type without creating them manually. A class diagram that shows their static relationships is illustrated in Figure 3-1.
Prototype
declares an interface for cloning itself. ConcretePrototype
as a subclass of the Prototype
implements the clone
operation for cloning itself. The client here creates a new object by asking a prototype to clone itself.
THE PROTOTYPE PATTERN: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.[1]
We may think about using the Prototype pattern when
We need to create objects that should be independent of what they are and how they are created.
Classes to be instantiated are determined at runtime.
We don't want to have a hierarchy of factories for a corresponding hierarchy of products.
The differences between instances of different classes are just a few combinations of state. Then it's more convenient to clone a corresponding number of prototypes rather than instantiating them manually.
Classes are not easy to create, such as composite objects in which each component can have other components as children. It'd be easier to clone existing composite objects and modify the copies.
A common misconception about using the Prototype pattern is that a prototype object should be an archetypal object and, typically, one that is never actually used. That misconception focuses on a particular way to implement the pattern. From a functional point of view, a prototype object can be any object that should be better off cloning itself than getting instantiated manually. There are two particularly common situations where we would naturally think about using the pattern
You have many related classes whose behavior is slightly different, and they mostly differ in internal attributes, such as name, image, etc.
You need to use a composite (tree) object as a basis for something else, e.g., use a composite object as a building block to build another composite object.
There are a lot more situations where you'd apply this pattern in the real world. Using design patterns is more art than science. Break some rules, be creative, and work smarter!
The bottom line for this pattern is to make a true copy of an object so we can use it as a basis (prototype) for something else related in the same context. So our next stop is to discuss issues related to copying objects.
If an object has a pointer as a member variable that points to some resource in memory, how would you make a copy of that object? Would you just copy that pointer value and pass it to a new object as a clone? The pointer is just a placeholder that stores an address of some resource in memory. In a cloning operation, if a pointer is just copied over to a new object (a clone), the underlying resource is still actually being shared by both instances, as illustrated in Figure 3-2.
Figure 3-2. An illustration depicts a scenario of cloning ConcretePrototype1
by just copying the value of the Resource Pointer 1, but the actual resource is not copied.
In the clone
operation of ConcretePrototype
, it copies a pointer value of the Resource Pointer to a new clone. Even though the instance of ConcretePrototype
made another instance of the same type as its clone, the pointers in both instances are still pointing to the same resource in memory. So the pointer value is cloned but not the actual resource. We call this shallow copying.
So what's deep copying then? Deep copying is to copy not just pointer values over but also the resources being referenced by the pointers. A deep copying version of the same clone
operation is illustrated in Figure 3-3.
The clone
operation is to not just simply make a copy of the Resource Pointer to a new one but also to make a true copy of the actual resource in memory. So the pointer of the clone is pointing to a copy of the same resource (content) in memory but in a different location.
Figure 3-3. This is a similar scenario as in Figure 3-2, but the actual resource in memory is copied during the cloning operation.
The Cocoa Touch framework provides a protocol for any NSObject
descendent to implement deep copying. Any subclass of NSObject
needs to implement the NSCopying
protocol and its method, (id)copyWithZone:(NSZone *)zone
. NSObject
has an instance method called (id)copy
. The default copy
method calls [self copyWithZone:nil]
. For subclasses that adopt the NSCopying
protocol, that method needs to be implemented, otherwise it will raise an exception. In iOS, this method retains the newly cloned object before returning it. The invoker of this method is responsible for releasing the returned object.
Most of the time, an implementation for deep copying doesn't look very complicated. The idea is to copy any necessary member variables and resources, pass them to a new instance of the same class, and return the new instance. The trick is to make sure you do really copy the resource in memory and not just pointer values. It's more apparent when you need to deal with a composite structure like the one for the TouchPainter app in Chapter 2.
In our original TouchPainter app, one of the most important data structures is the Mark
aggregate object that contains all Dots
and Strokes
instances for a scribble. There are at least two situations that require the cloning of the whole structure:
Make a deep copy of it and archive it.
Reuse the same scribble as a "pattern template" (rubber stamp) to repeat the same pattern as the user draws.
For the first one, we need to make sure the copy won't be modified while it's being kept for archiving or other related processes as compared to just retaining a reference to the original aggregate (see Memento pattern in Chapter 23 for details).
If we allow the user to reuse the same scribble as a basis for other strokes, then we need to let the current scribble copy itself so it can be used as a "pattern template" feature.
We are going to adopt a composite structure (Composite pattern, Chapter 13) that contains the user's strokes in the TouchPainter app. The composite structure is illustrated in Chapter 2 as an integral part of the app. The parent of the structure called Mark
is defined as an Objective-C protocol. Mark
has three concrete classes (components) that define a composite object at runtime. Dot
and Vertex
define individual components while Stroke
defines composite components that can also contain other Mark
instances as children. Dot
is a subclass of Vertex
. The challenge here is to make an exact deep copy of the whole Mark
aggregate structure without knowing how to parse the whole tree. A Stroke
object can contain objects of Dots
or its subclasses as well as other Stroke
objects. We need to modify the original Mark
and its implementing classes to have a "copy" method that will do the recursive deep copying trick. Figure 3-4 illustrates a class diagram of the Mark
composite classes with a copy
method.
There is not much change in the interfaces of Mark
and its concrete classes except a copy
method is added to each of them. There is a fictitious client that does savePattern
with a currentMark
as a copy called pattern
for later use. How to use the copies depends on the application, but the rule of thumb is we ask objects to create themselves when it's too complicated to recreate by a client, like reconstructing an existing Mark
composite object, for example.
Now let's look at some code.
Listing 3-1 illustrates changes in the Mark
protocol.
Example 3-1. Mark.h
@protocol Mark <NSObject>
@property (nonatomic, retain) UIColor * color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count; // number of children
@property (nonatomic, readonly) id <Mark> lastChild;
- (id) copy;
- (void) addMark:(id <Mark>) mark;
- (void) removeMark:(id <Mark>) mark;
- (id <Mark>) childMarkAtIndex:(NSUInteger) index;
@end
We have added a new copy
method to the Mark
protocol that returns an instance of an implementing class.
There are a few implementing classes that will implement the copy
method. Classes of their objects that will be part of a Mark
aggregate at runtime are Vertex
, Strokes
, as well as a subclass of Vertex
, Dot
. Dot
objects are to represent a single dot drawn on the screen as opposed to a Stroke
object. A Stroke
object can be the grandparent of all types of Mark
as well as just a simple parent that contains Vertex
objects to form a complete stroke. Vertex
doesn't do anything except maintain the location (coordinates) on the screen, so a rendering algorithm can draw them as a stroke (see Composite pattern in Chapter 13 and Visitor pattern in Chapter 15).
A visual diagram that depicts their relationships is illustrated in Figure 3-5.
We will continue to implement the copy
method for the implementing classes of the Mark
protocol. The first one is Vertex
in Listing 3-2.
Example 3-2. Vertex.h
#import "Mark.h"
@interface Vertex : NSObject <Mark, NSCopying>
{
@protected
CGPoint location_;
}
@property (nonatomic, retain) UIColor *color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) id <Mark> lastChild;
- (id) initWithLocation:(CGPoint) location;
- (void) addMark:(id <Mark>) mark;
- (void) removeMark:(id <Mark>) mark;
- (id <Mark>) childMarkAtIndex:(NSUInteger) index; // needs to be added to draft
- (id) copyWithZone:(NSZone *)zone;
@end
Wait a minute—why is there no copy
method and copyWithZone:
instead? And why is there a copy
method in the Mark
protocol but not the copyWithZone:
method? The Mark
protocol adopts the NSObject
protocol while Mark
's concrete classes adopt Mark
and subclass NSObject
class. The NSObject
protocol doesn't have a copy
method declared, but NSObject
does. When a message of copy
is invoked on a receiver of NSObject
, NSObject
will in turn forward the call to its subclass that adopts the NSCopying
protocol. The subclass needs to implement the required copyWithZone:zone
method defined in NSCopying
to return a clone of itself. If the subclass doesn't implement the method, an instance of NSInvalidArgumentException
will be thrown. That's why we need to let the Vertex
class adopt the NSCopying
protocol and implement its copyWithZone:zone
method for the cloning process. However, the NSObject
protocol doesn't have a method copy
declared, so we also declared it in our Mark
protocol to avoid compiler warnings. Vertex
is a subclass of NSObject
, so it implements that copyWithZone:
method as in Listing 3-3.
Example 3-3. Vertex.m
#import "Vertex.h" @implementation Vertex @synthesize location=location_; @dynamic color, size; - (id) initWithLocation:(CGPoint) aLocation { if (self = [super init]) { [self setLocation:aLocation]; } return self; } // default properties do nothing - (void) setColor:(UIColor *)color {} - (UIColor *) color { return nil; } - (void) setSize:(CGFloat)size {} - (CGFloat) size { return 0.0; } // Mark operations do nothing
- (void) addMark:(id <Mark>) mark {} - (void) removeMark:(id <Mark>) mark {} - (id <Mark>) childMarkAtIndex:(NSUInteger) index { return nil; } - (id <Mark>) lastChild { return nil; } - (NSUInteger) count { return 0; } - (NSEnumerator *) enumerator { return nil; }#pragma mark -
#pragma mark NSCopying method
// it needs to be implemented for memento
- (id)copyWithZone:(NSZone *)zone
{
Vertex *vertexCopy = [[[self class] allocWithZone:zone] initWithLocation:location_];
return vertexCopy;
}
@end
In the overridden copyWithZone:
method, a new instance of Vertex
is created with [[self class] allocWithZone:zone]
and initialized with the current location. [self class]
is used because we want its subclasses to be able to reuse this copy method as well. If directly used [Vertex alloc]
, then its subclasses will just return a copy of Vertex
but not its own actual type. Vertex only implements the location
property, so once the new copy is initialized with the current location then vertexCopy
is returned.
Vertex
objects are used for composing strokes and don't contain any other information such as color and size. But when there is a single point created on the screen by the user, we need another type of data structure to contain color and size to represent that dot. Besides color and size, location is also crucial to an individual dot. So we create a subclass of Vertex
called Dot
as shown in Listing 3-4.
Example 3-4. Dot.h
#import "Vertex.h"
@interface Dot : Vertex
{
@private
UIColor *color_;
CGFloat size_;
}
@property (nonatomic, retain) UIColor *color;
@property (nonatomic, assign) CGFloat size;
- (id) copyWithZone:(NSZone *)zone;
@end
Similar to Vertex, it implements the copyWithZone:
method to support copying in conjunction with the protocol. Its implementation of the method is shown in Listing 3.5.
Example 3-5. Dot.m
#import "Dot.h" @implementation Dot @synthesize size=size_, color=color_; - (void) dealloc { [color_ release]; [super dealloc]; }#pragma mark -
#pragma mark NSCopying delegate method
- (id)copyWithZone:(NSZone *)zone
{
Dot *dotCopy = [[[self class] allocWithZone:zone] initWithLocation:location_];
// copy the color
[dotCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
// copy the size
[dotCopy setSize:size_];
return dotCopy;
}
@end
The first initialization statement is almost the same as Vertex
. Dot
supports two more attributes types, so it also needs to set the color
and size
properties with its own color_ and size_
private variables on top of location
. Both location
and size
can be assigned to a copy with zero concern. However, color
needs some attention here. We need to make a copy of UIColor
with an existing CGColor
value in the color_
variable. Finally the method returns dotCopy
.
Let's move on to the Stroke
class as shown in Listing 3-6.
Example 3-6. Stroke.h
#import "Mark.h"
@interface Stroke : NSObject <Mark, NSCopying>
{
@private
UIColor *color_;
CGFloat size_;
NSMutableArray *children_;
}
@property (nonatomic, retain) UIColor *color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) id <Mark> lastChild;
- (void) addMark:(id <Mark>) mark;
- (void) removeMark:(id <Mark>) mark;
- (id <Mark>) childMarkAtIndex:(NSUInteger) index;
- (id) copyWithZone:(NSZone *)zone;
@end
Then let's see its implementation in Listing 3-7.
Example 3-7. Stroke.m
#import "Stroke.h" @implementation Stroke @synthesize color=color_, size=size_; @dynamic location; - (id) init { if (self = [super init]) { children_ = [[NSMutableArray alloc] initWithCapacity:5]; } return self; } - (void) setLocation:(CGPoint)aPoint { // it doesn't set any arbitrary location } - (CGPoint) location { // return the location of the first child if ([children_ count] > 0) { return [[children_ objectAtIndex:0] location]; } // otherwise returns the origin return CGPointZero; } - (void) addMark:(id <Mark>) mark { [children_ addObject:mark]; } - (void) removeMark:(id <Mark>) mark
{ // if mark is at this level then // remove it and return // otherwise, let every child // search for it if ([children_ containsObject:mark]) { [children_ removeObject:mark]; } else { [children_ makeObjectsPerformSelector:@selector(removeMark:) withObject:mark]; } } - (id <Mark>) childMarkAtIndex:(NSUInteger) index { if (index >= [children_ count]) return nil; return [children_ objectAtIndex:index]; } // a convenience method to return the last child - (id <Mark>) lastChild { return [children_ lastObject]; } // returns number of children - (NSUInteger) count { return [children_ count]; } - (void) dealloc { [color_ release]; [children_ release]; [super dealloc]; }#pragma mark -
#pragma mark NSCopying method
- (id)copyWithZone:(NSZone *)zone
{
Stroke *strokeCopy = [[[self class] allocWithZone:zone] init];
// copy the color
[strokeCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
// copy the size
[strokeCopy setSize:size_];
// copy the children
for (id <Mark> child in children_)
{
id <Mark> childCopy = [child copy];
[strokeCopy addMark:child];
[childCopy release];
}
return strokeCopy;
}
@end
The Stroke
class implements the same copyWithZone:
method as the Vertex
and Dot
classes. But for Stroke
, it needs to copy not only attributes that are defined in Mark
but also its children (a for
loop in the foregoing code). Each child in the children_
collection variable needs to make their own copy first, and then a Stroke
object will add that cloned child to a cloned strokeCopy
as a new child. When everything is cloned and set, it will return the strokeCopy
as a clone of the original Stroke
object.
You may have noticed that the location
property that was declared in the Mark
protocol is now defined as @dynamic
in the @implementation
scope of Stroke
. A stroke doesn't keep its own location on a view but just returns the location of its first child, if there is one. There are two overridden accessor methods for the location
property to handle the special case. setLocation:
doesn't do anything, while location
returns the screen coordinates of the stroke's first child. We use @dynamic
to tell the compiler not to generate those accessor methods for us as opposed to using @synthesize
because we create own version of them. The details of the composite structure are discussed in Chapter 13.
We know that a Stroke
object can contain any Mark
reference as a child. A particular type is the Vertex;
its purpose is to provide the location information to a drawing algorithm. Vertex
is a direct subclass of Dot
. Dot
already has that copyWithZone:
method implemented, as shown in Listing 3-3.
Now we have all the copying hocus-pocus set up, so we are ready to discuss how to use the prototype pattern to implement the "Pattern Template" feature.
Now each subclass of Mark
is clonable and able to return a true copy of it. One important use of object copying is for saving the state of it. In other words, a snapshot of what the object is at the moment when it is cloned (copied). When does it need to be copied? Undo and redo. Before executing an operation, the state of an object (e.g., a document) needs to be saved in some way. Then the state is pushed to an undo stack. When an undo operation is requested, the last state of the object (document) will be restored back to the application. Most of the time, copying objects is complicated and error-prone. If clients handle the process, the client code may need to be changed every time there are some changes in a target class (e.g., document again). When we are talking about copying objects, we may be thinking only about some simple objects with just a few member variables and interfaces. However, things can go pretty wild when you need to deal with composite objects. It would be very complicated to clone a composite object just like that. A composite object could be very heavy-weighted and tricky to handle. You can picture traversing the whole structure and making new instances of each node with corresponding attributes. What's even worse is that we don't know the exact type of each node in the structure and that makes it even more complicated to create them externally. The implementation of prototype in the Mark
family is not just a lifesaver for such situations but essential for the "Pattern Template" feature that we are going discuss in this section.
We want to reuse a particular stroke and make it as a "pattern template" to apply it to the CanvasView
as a basis for drawing different things. The idea is to copy a particular Mark
(either Stroke
or Dot
) and paste it on the CanvasView
so the user can reuse the same pattern over and over again. A sample drawing based on that idea is shown in Figure 3-6.
Figure 3-6. A pattern with both original and cloned strokes—a black stroke indicates the original stroke. Gray strokes are copies of the black one.
The black stroke is the original stroke that was saved before as a pattern template. The gray ones are the clones of it. Both the black and gray strokes have the same shape. The cloned strokes may have different attributes in terms of color and location (or even transformation). The user can select whatever stroke onscreen and set it as a pattern template to be reused later. Each time the user applies the pattern, the application makes a copy out of the original and puts it on the CanvasView
. Then the user can change the attributes, such as color, size, and location, of the stroke copies. A final structure that comprises the original stroke and the copies can be saved as another pattern template. The possibilities are unlimited.
We are not going too deep into this feature, but we will discuss the idea with some code snippets. I will leave this part for you to implement in the sample project as an exercise. Let's assume that we have an instance of Mark
that was selected by the user as a pattern template called selectedMark
. We will make a copy of it and save it in a data structure called templateArray
, as shown in the snippet here.
id <Mark> patternTemplate = [selectedMark copy]; // save the patternTemplate in // a data structure so it can be // used later [templateArray addObject:patternTemplate];
At this time, we have no idea what the user exactly selected. All we know is it's an instance of Mark
aggregate that could have just one Dot
or multiple Strokes
and each of them has multiple Vertex
in it. With just a simple yet magical copy
message sent to the selectedMark
, we have a perfect copy of it. Now when the user wants to apply one of the previously saved pattern templates to the CanvasView
, we will need to get it from the templateArray
with a patternIndex
provided by the user and append it to a current Mark
composite. Then the new one will become part of the current one, as shown in the following snippet.
id <Mark> patternClone = [templateArray objectAtIndex:patternIndex]; [currentMark addMark:patternClone]; [canvasView setMark:currentMark]; [canvasView setNeedsDisplay];
After we set the canvasView
with an updated Mark
instance, we send a setNeedsDisplay
message to it to draw the Mark
on the screen.
Obviously we can't add a copy of a stroke on the screen just like that without at least modifying its location; otherwise it may cover the original one if it's still there. But the code snippets should be clear enough to showcase the idea of how to use the Prototype pattern that we have implemented in Mark
to make this TouchPainter app more magical.
In this chapter, we have explored how to use the Prototype pattern to implement copying for the Mark
composite structure in the TouchPainter app. A deep copying implementation is necessary for a Mark
aggregate that contains a tree structure when we need to make a true copy of it. An example in the Memento pattern discussed in Chapter 23 keeps a copy of a complete Mark
aggregate before saving it in a memento object.
The Prototype pattern is one of the simplest and easiest patterns for object creation. In the next chapter, you will see another object creation pattern that doesn't use a copy
method to create the same type of object, but a method that decides what type of object to create.
[1] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
18.189.188.238