You can picture a composite as an entity that contains other entities of the same kind. The whole bundle is like a tree with connected entities of parents and children. It's like a family tree of the same ancestry. Every node (child) in the family tree has the same family name. For other people who want to refer to my family, they need to refer to the Chungs. So every member in my family should be covered. So if a question like "Hey, Chungs, would you please give me your $5?" was asked, then every member would shell out $5. Requests can go recursive if we ask, "Chungs, would you please give me a total number of members in the family?" We assume the great-grandfather of the family will receive the message; the operation will be passed down through the bottom of the tree. Each individual family (composite) in the tree that contains children will return a sum of total number of children with other numbers returned from the children as well. What about unmarried members (leaf nodes) in the family? They will return zero as a result. After the great-grandfather collects all numbers returned from other family members in the tree, he will sum them up and return the sum to the requester as an answer. Instead of asking each individual member (or family) in the Chungs' family tree for some particular tasks, one can send a message to the Chungs (a tree) as the operation on the whole.
We borrow a similar idea in object-oriented software design. Composite structures can be very complex, and their internal representation is not supposed to be exposed to clients. We need to refer to the whole complex structure as a whole with a unified interface, so clients don't need to figure out what a particular node is in order to use it.
In this chapter, we are going to discuss the concepts of the Composite pattern. We will also use the TouchPainter app from Chapter 2 to discuss how to implement the pattern. Later in the chapter, we will discuss how the Cocoa Touch framework adapts the pattern in the UIView
architecture.
The Composite pattern allows you to compose objects that have the same base type into tree-like structures in which parent nodes contain child nodes of the same type. In other words, that kind of tree-like structure forms part-whole hierarchies. What is a part-whole hierarchy? It's a hierarchical structure that contains both compositions of objects (containers) and individual objects as leaf nodes (primitives). Each composite has other nodes that could be either leaf nodes or other composites. That kind of relationship repeats itself within the hierarchy recursively. Since every composition or leaf node shares the same base type, the same operations can be applied over each of them without type-checking on the client side. A client can ignore the differences between a composition and a leaf node to operate them. An example of a composite object structure at runtime is illustrated in Figure 13-1.
A static structure of the Composite pattern is illustrated as a class diagram in Figure 13-2.
The base interface is the Component
that defines operations that are shared by both the Leaf
and the Composite
classes.
There are some operations that are meaningful to only the Composite
class, such as add:Component
, remove:Component
, and getChild:int
. The Leaf
class doesn't implement those methods for an obvious reason (in fact, it does but just an empty implementation). Why don't we put those methods in the Composite
class only? We don't want clients to tell which kinds of nodes they are dealing with at runtime and to expose the internal details of the composite structure to clients as well. That's why even though operations make sense only to the Composite
class; we also declare them in the base interface so every type of node share the same things. That way we can let clients treat any one of them uniformly.
Each node represents either a leaf node or a composite node. The main difference between the Leaf
node and the Composite
node is that a Leaf
node doesn't contain any children of the same type but Composite
does. Composite
contains children of the same base type. Since both the Leaf
and Composite
classes share the same interfaces, any operation that is targeted for the Component
type can be safely applied to each of them. Clients don't need to care about type-checking with a bunch of if- else
, switch-case
statements to determine the exact type of the object that is being worked on.
Let's back up to the top level of the hierarchy. The grandparent of all these nodes is treated as a reference to an instance of the Component
class. If there are any changes to the internal structure, there is no client code that needs to be changed.
At the ultimate parent node, the whole aggregate structure can be referred to as their common base type, Component
. Clients don't need to know any traversal strategy to use it, as a traversal or enumeration strategy is either provided by a composite structure or accessed through an external/internal iterator (see the Iterator pattern, Chapter 14).
THE COMPOSITE PATTERN: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.[11]
You would naturally think about using the pattern when
You want to have abstract tree representation of objects (part-whole hierarchies).
You want clients to treat all objects in the composite structure and individual objects uniformly.
In Chapter 2, we briefly discussed using an abstract tree structure to maintain user-created (drawn) strokes. In the next few sections, we are going to discuss how to implement the pattern in the TouchPainter app to achieve that.
Let's go back to our Mark
composite structure that was defined in Chapter 2. We have a base Mark
protocol that acts as a base type of both of our individual Dot
, Vertex
, and Stroke
types. Instances of Dot
are drawable on a view, while Stroke
's child Vertex
objects are used only for helping to connect lines together within the same stroke.
Clients that know how to construct a tree of Mark
composite structures will add instances of Vertex
to a Stroke
instance and add the Stroke
instance to the grandparent of the scribble. For adding drawable dots, the clients need to add individual instances of Dot
to the grandparent. Their relationships are illustrated in Figure 13-3.
Figure 13-3. A class diagram of a Composite structure shows the class relationships of Dot
and Vertex
as leaf node classes as well as Stroke
as a container class.
Some people have questions like: Does the client having to know when to add a Dot
vs. when to add a Vertex
violate the encapsulation of the Composite pattern? Doesn't it defeat the purpose of the pattern if the client needs to know if they are working with a Stroke
, a Vertex
, or a Dot
?
The answer for both questions is no. It's simply because there are two types of clients; one is a creator of a composite structure while the other type populates or manipulates the structure. Creators need to know what to put into the structure. Other clients that use the structure don't need to care about the exact types of the nodes in it.
Vertex
implements only the location
property. Dot
subclasses Vertex
and adds color
and size
because Vertex
doesn't need color
and size
but Dot
does. A visualized runtime structure of a Mark
tree is shown in Figure 13-4.
aStroke
can contain either Dot
(aDot
) or Vertex
objects (aVertex
) at runtime. So a Stroke
object can be a parent of all kinds of Mark
s or a composition of a real stroke that is made out of Vertex
objects and drawn on the screen as a whole group.
Let's cut some code for the Mark
protocol as shown in Listing 13-1.
Example 13-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; @property (nonatomic, readonly) id<Mark> lastChild; - (void) addMark:(id <Mark>) mark; - (void) removeMark:(id <Mark>) mark; - (id <Mark>) childMarkAtIndex:(NSUInteger) index; @end
Mark
has three properties, color
, size
, and location
. They should be common for any kinds of Mark
s that are drawable on the screen. In our original class diagrams, there are two methods declared in the Mark
type, addMark:mark
and removeMark:mark
. They seem to make sense only to the Stroke
class, as a Dot
instance doesn't have any composition at all. The reason composite operations are declared in the top-level abstract interface is that we don't want clients to do any runtime check to know whether a Mark
instance is a Stroke
in order to add or remove children. Our goal is to make every node the same to clients to use. This uniformity becomes obviously important, for example, if you need to write a method that takes a parameter of Mark
and adds/removes other Mark
objects to/from it. The method shouldn't need to know whether the Mark
of interest is a Stroke
or Vertex
(or Dot
).
Now, here we go with our Vertex
class, as shown Listing 13-2.
Example 13-2. Vertex.h
#import "Mark.h" @interface Vertex : NSObject <Mark> { @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; @end
It's a good practice to re-declare properties/methods that the subclass overrides. Even though Vertex
seems to implement all properties declared in the Mark
protocol, only the location
property is real. The location_
member variable is declared as @protected
as it will be used by at least one subclass later. The rest of the properties do nothing (dummies), as shown in Listing 13-3.
Example 13-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; } @end
Vertex@synthesize
s only the location
property and @dynamiccolor
and size
. @dynamic
tells the compiler that we provide our own accessor methods for color
and size
. Although the compiler may not complain about it if we take the @dynamic
directive off, it's still a good practice to keep that as it will make your life easier when you come back to the code later. The initWithLocation:
method simply assigns the aLocation
value to the Vertex
's location
property and returns itself. Other composite methods declared in Mark
are dummies.
Dot
subclasses Vertex
and provides a few more implementations for the Mark
protocol, as shown in Listing 13-4.
Example 13-4. Dot.h
#import "Vertex.h" @interface Dot : Vertex { @private UIColor *color_; CGFloat size_; } @property (nonatomic, retain) UIColor *color; @property (nonatomic, assign) CGFloat size; @end
Dot
needs to @synthesize
only the size
and color
properties, as Vertex
already covers a lot of stuff that is good enough for Dot
as well. Dot
's implementation is shown in Listing 13-5.
Example 13-5. Dot.m
#import "Dot.h" @implementation Dot @synthesize size=size_, color=color_; - (void) dealloc { [color_ release]; [super dealloc]; } @end
We are done with Vertex
and Dot
. Let's move forward to Stroke
, our composite class, as shown in Listing 13-6.
Example 13-6. Stroke.h
#import "Mark.h" @interface Stroke : NSObject <Mark> { @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; @end
As Stroke
is a composite class, unlike Vertex
and Dot
, it does need to implement all the methods and properties declared in the Mark
protocol, as shown in Listing 13-7.
Example 13-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]; } } // needs to be added to draft - (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]; } @end
Stroke
has its own children_
as an instance of NSMutableArray
to keep track of its child Mark
nodes. In its init
method, it initializes an instance of NSMutableArray
with the arbitrary capacity of five (it doesn't hurt to make it a little bigger, though).
The location
property is not @synthesize
d because a Stroke
object doesn't provide a location
value directly but from its first child. If the Stroke
object doesn't have any children, the location
accessor method will return CGPointZero
that refers to the origin of the screen.
The addMark: (id <Mark>) mark
method forwards the mark
parameter to children_
with the message call addObject:mark
. removeMark: (id <Mark>) mark
is a little tricky. It first searches for the mark parameter at the children_
level. If there is none, then it will forward the search operation to each child node with the message makeObjectsPerformSelector:@selector(removeMark:) withObject:mark
, so the search will keep going recursively until it reaches the last child of the tree. Every Mark
object implements the removeMark:
method, so there is no concern for us whether a particular child node is a Stroke
, Vertex
, or anything else before sending the message out.
childMarkAtIndex:
, lastChild
, and count
methods simply return from similar methods in children_
(NSMutableArray
).
The way a client can construct a Mark
composite structure is shown in Listing 13-8.
Example 13-8. Client Code That Constructs a Mark
Composite Structure with Dot
, Vertex
, and Stroke
Objects
// ... Dot *newDot = [[[Dot alloc] init] autorelease]; Stroke *parentStroke = [[[Stroke alloc] init] autorelease]; [parentStroke addMark:newDot]; // ... Vertex *newVertex = [[[vertex alloc] init] autorelease]; Stroke *newStroke = [[[Stroke alloc] init] autorelease]; [newStroke addMark:newVertex]; [parentStroke addMark:newStroke]; // ...
Individual Dot
objects can be added to parentStroke
as leaf nodes. parentStroke
can also take composite Stroke
objects that manage their own Vertex
children, of which the purpose is to let the drawing algorithm draw connected lines.
In our TouchPaint app, there is a CanvasViewController
that handles some basic drawing operations with touches. Touch event operations in the controller construct a main composite Mark
structure that contains all individual, drawable dots as well as strokes that contain other non-drawable vertices, to form connected lines. CanvasViewController
's view, called CanvasView
, will present (draw) a complete Mark
structure on the screen.
Figure 13-5 illustrates an actual dot and stroke drawing situation.
We can add a drawing operation like drawWithContext:(CGContextRef)context
to the Mark
protocol so every node can draw itself according to its specific purpose. The one in Dot
will go like that shown in Listing 13-9.
Example 13-9. drawWithContext:
Implementation in Dot
- (void) drawWithContext:(CGContextRef)context { CGFloat x = self.location.x; CGFloat y = self.location.y; CGFloat frameSize = self.size; CGRect frame = CGRectMake(x, y, frameSize, frameSize); CGContextSetFillColorWithColor (context,[self color CGColor]); CGContextFillEllipseInRect(context, frame); }
With an active context, an ellipse (a dot) can be drawn in the context with location, color, and size.
However, a vertex provides only a particular location in a stroke. So a Vertex
object will add a point to a line only in the context (well, the Quartz 2D function in fact does add a line to a point) with its own location (coordinates), as shown in Listing 13-10.
Example 13-10. drawWithContext:
Implementation in Vertex
- (void) drawWithContext:(CGContextRef)context { CGFloat x = self.location.x; CGFloat y = self.location.y; CGContextAddLineToPoint(context, x, y); }
For a Stroke
object, it needs to move the context to the first point of its children, and then it wraps up the whole line drawing operation with the Quartz 2D functions CGContextSetStrokeColorWithColor
and CGContextStrokePath
, as shown in Listing 13-11.
Example 13-11. drawWithContext:
Implementation in Stroke
- (void) drawWithContext:(CGContextRef)context { CGContextMoveToPoint(context, self.location.x, self.location.y); for (id <Mark> mark in children_) { [mark drawWithContext:context]; } CGContextSetStrokeColorWithColor(context,[self.color CGColor]); CGContextStrokePath(context); }
The primary challenge when designing the Mark
protocol is to come up with a minimal set of operations to provide open-ended functionality. Performing surgery on the Mark
protocol and its subclasses for each new functionality is both invasive and error-prone. Eventually, the classes get harder to understand, extend, and reuse. So the key is to focus on a sufficient set of primitives for a simple and coherent interface.
In the chapter about the Visitor pattern (Chapter 15), we will explore how both the Composite and the Visitor patterns work together to create powerful yet flexible solutions for many problems related to tree structures.
In the Cocoa Touch framework, UIView
s are organized as a composite structure. Each instance of UIView
can have other instances of UIView
s to form a unified tree structure. It lets clients treat individual UIView
objects and compositions of UIView
s uniformly.
The UIView
s in a window are internally structured into a view tree. At the root of the hierarchy is a UIWindow
object and its content view. Other UIView
s that are added to it become subviews of it. Each of them can have other views and becomes the superview of its view children. A UIView
object can have only one superview and zero or more subviews. Figure 13-6 illustrates this relationship.
Their relationships can be viewed from a different perspective as illustrated in a hierarchical diagram in Figure 13-7.
The view composite structure plays a part in both drawing and event handling. When a superview is asked to render itself for display, the message will be handled in the superview before its subviews. The message will be propagated to other subviews throughout the whole tree. Since they are all of the same type, UIView
, they can be treated uniformly, and a branch of a UIView
hierarchy can be treated as a unified view.
The unified view is also used as a responder chain for event handling and action messages. The drawing message is passed down the structure from superviews to their subviews, as are any event handling messages. The responder chain in the Cocoa Touch framework is the implementation of the Chain of Responsibility pattern (see Chapter 17).
The Composite pattern's main intent is for each node in a tree structure to share the same abstraction interface. So the whole structure can be used as a unified abstraction structure without exposing its internal representation. Any operation on each node (either leaf or composite) can be operated with the same interfaces that are defined in a protocol or abstraction base class.
New operations applied on the structure can be used with the Visitor pattern (Chapter 17) to have visitors "visit" each node for further processing without modifying the existing composite structure.
An internal representation of a composite structure is not supposed to be exposed to clients, so the Composite pattern is always used with the Iterator pattern to enumerate each individual item in a composite object. We will discuss the Iterator pattern and related design issues in the next chapter.
[11] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
18.225.149.238