Summary prepared by Kirk Scott Unit 25 Composite Summary prepared by Kirk Scott
Design Patterns in Java Chapter 5 Composite Summary prepared by Kirk Scott
Composites—The Introduction Before the Introduction The composite design pattern is extremely well-defined It is not ambigous It can be shown clearly with a straightforward UML diagram The book’s definition, although interesting, doesn’t really tell you what the pattern is
There is no use in having an elaborate introduction before the introduction, defining terms, so that the definition will be clear However, one introductory comment might be worthwhile At the beginning of the course, this general observation was made: In a sense, data structures and algorithms can be viewed as “micro” concerns about the implementation internals of classes
Correspondingly, design patterns can be viewed as “macro” concerns about the relationships between classes The composite design pattern sort of shows that although maybe useful, that distinction is not necessarily true The composite pattern, defined as relationships among classes, is the basis for making tree-like data structures of relationships between objects of the classes
Without further ado, the book’s definition will simply be given on the following overhead This will be followed directly by an explanation of what the pattern is, which the definition doesn’t really make clear
Book Definition of Pattern The intent of the Composite pattern is to let clients treat individual objects and compositions of objects uniformly
Concrete Explanation The composite design pattern is based on the relationships between three classes/interfaces: Component Composite Leaf These things will be briefly defined Then the UML diagram will be shown Then more discussion will follow
The component is fundamental to the pattern A component is an abstract class or interface 1. It can be implemented by a class (Leaf) representing individual items 2. Or it can be implemented by a class (Composite) that represents a collection of such items
A Composite contains a collection of components as defined above This means that the composite can contain individual items (leaves) It can also contain collections of items (other composites) This is important: Composites can contain instances of other composites
The Leaf is an implementation of Component which consists of individual items The class is called Leaf because instances of classes in these relationships form trees It is important to note that in this design pattern, so-called leaf items are not restricted to the outermost branches of the tree
The UML diagram on the following overhead illustrates the structure of the Composite design pattern
Structurally, this the fundamental characteristic of the pattern The Composite class can have instances of Component, and Composite is a subclass of Component
Composite and Decorator If you think back to decorator, we’ve seen something long these lines before A decorator class contained a reference to an instance variable which was typed to a superclass of the decorator Composite takes this idea so much further that it results in something entirely different A composite can contain a collection of references to objects which are typed to the superclass
What Kind of Tree Does Composite Form? Objects related in this way form a particular kind of tree Do not be misled by the term “leaf” Leaves can be contained in internal nodes of the tree Take a directory structure for example Any directory (a composite) can contain individual files (leaves) as well as additional directories (composites)
Operations Notice that the UML diagram had methods in it named operation() and other() Components are either composites or leaves Behaviors (actions, operations, methods) declared in the Component superclass have to be defined/implemented in the subclasses so that they make sense for both individual items and collections of items
Clients The word client appears in the definition of this pattern The design pattern provides a means of making certain capabilities available to user written code
No client class is shown in the UML diagram In general, you would expect a client to have a reference to a (concrete) composite That composite would be the root of a tree The descends through the “has-a” relationships of the composite and the components it contains This is effectively a recursive situation, until you reach contained items which are leaves only
The Component Class Defines a (Generic, User) Interface The Component class is shown in the UML diagram as an abstract class The Component class could also be implemented as an interface The Component class provides a generic interface that the Leaf class and the Composite class have to conform to
The Definition, Again As stated in the definition, the intent of the Composite pattern is to let clients treat individual objects and compositions of objects uniformly It is the Component part of the design that defines this uniform interface It is the subclasses that implement it
Since Component is implemented as an abstract class is, it can contain some inheritable method implementations The subclasses, Leaf and Composite, may have methods of their own, may override inherited methods, and have to implement abstract methods
The Pattern and Polymorphism Interfaces, polymorphism, and dynamic binding play a key role in this design pattern An instance of Composite can contain more than one different kind of thing What it can contain is defined by the Component A given method can be called on the contents of a composite Different versions of the method will be used depending on whether the element is a leaf or another composite
Book Challenge The UML diagram is repeated on the next overhead for reference in the challenge that follows The challenge will be easy and repetitive if the foregoing explanation was clear
Challenge 5.1 Why does the Composite class in Figure 5.1 maintain a collection of Component objects instead of simply a collection of leaves?
Solution 5.1 Designing the Composite class to maintain a collection of Component objects lets a Composite object hold either Leaf objects or other Composite objects.
Solution 5.1, cont’d. In other words, this design lets us model groups as collections of other groups. For example, we might want to define a user’s system privileges as a collection of either specific privileges or other groups of privileges. As another example, we might want to be able to define a work process as a collection of process steps and other processes.
Solution 5.1, cont’d. Such definitions are much more flexible than defining a composite to be a collection of leaves. If we allowed only collections of leaves, our “composites” could be only one layer deep.
Comment mode on: This last part of the book’s answer raises a question, which will be stated below, but not pursued Could you come up with a design pattern that created multi-layer trees where there were internal nodes and leaf nodes, but only the lowest layer of internal node code have a leaf node?
Comment mode cont’d. A directory structure is probably the example of the pattern most familiar to computer people The pattern also arises in manufacturing (databases) where assemblies consist of parts and subassemblies
Oozinoz Example The book, as usual, has an example from the Oozinoz domain Briefly, there are individual machines that are components of the manufacturing process These are the leaves
There are also machines which are compositions of other machines These compositions are collectively thought of as single, composite components of the manufacturing process Any given machine, i.e., component, may consist of a combination of single machines or composite machines
The concrete example is given in the following UML diagram Note that the diagram includes a method, getMachineCount() The details of the pattern will become apparent when deciding how to implement this method
Note that in the diagram the superclass, MachineComponent, is shown in italics MachineComponent is an abstract class The method getMachineCount() is also in italics and abstract The subclasses have to implement this abstract method They might also inherit useful concrete methods
Note also that the implementation of MachineComposite is based on the Java collection class List The MachineComposite class has an instance variable which is a list containing references to the components that an object of the class contains Compare this with cups containing ArrayLists of seeds
Component Method Implementation In general, when implementing methods in the subclasses, the implementation for a leaf will be a “one shot deal” It is reasonable to expect that implementation for composite class will involve iterating over the elements of the component list Because the two classes have a common interface, they will both implement methods with the same name
In the implementation of the method in the composite, when iterating over the component list, the list may contain references to both leaves and composites When iterating over the elements of the list, the same method name can be called on each element
Polymorphism and dynamic binding make this possible If an element of the list is a leaf, Machine, calling the method will cause the leaf version of the method to be used If an element of the list is a composite, MachineComposite, calling the method will cause the composite version of the method to be used
In this example, elements of the list will be typed MachineComponent and the method in question is getMachineCount() The Machine class will have a version of the method that simply returns the value 1 The MachineComposite class will have a version of the method that iterates over its components list, calling the method on its elements, accumulating a count
Challenge 5.2 Write the code for the getMachineCount() methods implemented by Machine and MachineComposite
Comment mode on: It should be apparent that getMachineCount() simply returns 1 for a single, simple instance of the Machine class As suggested by the earlier discussion, the implementation in the MachineComposite class will involve iterating over its list of components, accumulating the return values of recursive calls to getMachineCount() on them
Solution 5.2 For the Machine class, getMachineCount() should be something like: public int getMachineCount() { return 1; }
The class diagram shows that MachineComposite uses a List object to track its components. To count the machines in a composite, you might write:
public int getMachineCount() { int count = 0; Iterator i = components.iterator(); while(i.hasNext()) MachineComponent mc = (MachineComponent) i.next(); count += mc.getMachineCount(); } return count; If you’re using JDK 1.5 [or later], you may have used an extended for loop [a for each loop].
The Composite Design Pattern and Recursion Notice that the MachineComposite implementation of getMachineCount() is both iterative and recursive at the same time For any given node, in essence, you iterate over its children On each child you make a recursive call to get its machine count
In the case of a simple machine, there is no further recursion In the case of a composite, the pattern is repeated of iterating over the children getting the machine count
In a previous unit, a made-up phrase was used: dynamic binding recursion This is effectively happening here: You iterate over list elements that are typed to the superclass, MachineComponent The counting method is called on these objects, whether Machines or MachineComposites, until there is no composite left to disassemble Then all that will trickle back up are sums of integer counts
Other Methods that might be Implemented The book suggests a few more methods that might be added to the composite example based on machines They are given in the following table
The book observes that you would expect the implementation of each of these methods in the MachineComposite class to be recursive This is not a surprise, considering that the implementation of getMachineCount() was recursive The book gives challenge 5.3 in the form of the table on the next overhead
Solution 5.3 See the next overhead
Composites, Trees, and Cycles The book seems to make this a little more complicated than necessary In most cases, it seems likely that the logic of a composite would require that it be a tree If this is the case, then it might be helpful to make sure the code included implementation of the functionality described on the next overhead
At the very least, include a method or methods that made it possible to check whether a created structure was a tree Or, include code in method implementations that disallowed adding nodes in such a way that a non-tree structure resulted
On the other hand, maybe the code doesn’t prevent the creation of non-tree structures and something that isn’t tree-like is either desirable, or could result inadvertently In this case, the implementations of methods like getMachineCount() would have to include code that made sure that iteration only counted elements once, even if the structure containing them was not a tree
An Examples that aren’t Trees In general, the presence of some sort of cycle in the data structure would mean that it wasn’t really a tree The book introduces some notation and simple graph theory in order to define trees and talk about cycles Objects in UML are represented by rectangles, and references between them are represented by solid lines with open arrowheads
The UML diagram on the following overhead shows a simple directed graph, which happens to be a path (a sequence of references from one object to another) This example clearly does not contain a cycle
A graph is a tree if: It has one node, a root node, that has no references to it And every other node has only one parent node, i.e., only one node that refers to it Notice how in this terminology arrows point from the top down This is different from the notation for inheritance, where the subclasses refer to their parents
The term cycle refers to a sequence of references where, starting at a given node, after following the sequence, you arrive at the same node again Any data structure that contains a cycle can’t be a tree
An Example without a Cycle that isn’t a Tree Consider the code shown on the next overhead Assume that you can construct instances of different kinds of simple and composite machines Also assume that there is an add() method that implements the navigability relationship shown by the open arrowheads in UML object diagrams There is no need to try and trace out what the code does That is immediately shown in the diagram that follows it
public static MachineComposite plant() { MachineComposite plant = new MachineComposite(100); MachineComposite bay = new MachineComposite(101); Machine mixer = new Mixer(102); Machine press = new StarPress(103); Machine assembler = new ShellAssembler(104); bay.add(mixer); bay.add(press); bay.add(assembler); plant.add(mixer); plant.add(bay); return plant; }
The next overhead shows the UML diagram of the relationships between the objects created by the foregoing code
This is probably not a desirable situation Logically, it would seem to make sense for a given object (the mixer) to be referred to only by its immediate parent (bay) not its ultimate parent (plant) However, the point is that it is possible to create this structure, and it’s necessary to be able to deal with it
It should be apparent that the result is not a tree It is somewhat unfortunate that the book has discussed “non-tree-ness” in terms of cycles in directed graphs As a directed graph, this diagram doesn’t show a cycle However, the mixer:Machine object in the diagram can be reached twice, via different, linked paths
In other words, mixer:Machine has two parent nodes, the List belonging to plant:MachineComposite and the List belonging to bay:MachineComposite For this reason the structure is not a tree, even though as a directed graph it doesn’t have a cycle If the graph were represented in non-directed form, then plant to bay to mixer to plant would essentially be a cycle
Method Code when Concerned with Cycles At any rate, such a structure is possible, and the task is to write correct implementations of the getMachineCount() method The straightforward implementation of the getMachineCount() method for a simple machine shown on the next overhead should still be correct
public int getMachineCount() { return 1; }
However, if a given node object can be reached twice, by different paths, the simple, recursive getMachineCount() method for MachineComposite given earlier will no longer be correct The code is repeated on the next overhead for reference Observe that if it were run on the structure previously shown, the mixer would be counted twice, and the machine count returned by the method would be 1 greater than the correct answer
public int getMachineCount() { int count = 0; Iterator i = components.iterator(); while(i.hasNext()) MachineComponent mc = (MachineComponent) i.next(); count += mc.getMachineCount(); } return count;
The book concretely illustrates the assertion concerning the current version of the code with a challenge Challenge 5.4 What does the program on the following overhead print out?
package app.composite; import com.oozinoz.machine.*; public class ShowPlant { public static void main(String[] args) MachineComponent c = OoozinozFactory.plant(); System.out.println(“Number of machines: “ + c.getMachineCount()); }
Comment mode on: The program shown on the previous overhead creates the non-tree structure diagrammed earlier It then calls the getMachineCount() method on the structure
Solution 5.4 The program prints out Number of machines: 4 There are, in fact, only three machines in the plant factory, but the mixer is counted by both plant and bay. Both of these objects contain lists of machine components that refer to the mixer
Solution 5.4, cont’d. The results could be worse. If, say, an engineer adds the plant object as a component of the bay composite, a call to getMachineCount() will enter an infinite loop
Comment mode on: Notice what they’re saying here This example was not a tree, but it also did not contain a cycle As a result, it gave an incorrect count
There second, verbal scenario, created a cycle That’s when you would enter an infinite loop From the point of view of the data structure created, this is a loop through the cycle From the point of view of the code, because the method implementation is recursive, this structural loop would cause an infinite sequence of recursive calls
Writing Correct Code for Handling Non-Tree Data Structures The book pursues the idea of dealing with non-tree structures in this way An application that allows structures to be created could check whether a node already exists in some component before allowing it to be added to the structure Checking to see whether a node already exists would be based on maintaining a set of existing nodes
The book introduces a method named isTree() in the Component class isTree() traverses a data structure, checking to see whether any node is encountered more than once If so, then isTree() returns false
The idea is that if you wanted to, you could include calls to isTree() in a (client) implementation in such a way that a node could not be added if it would cause isTree() to return false This would protect against the creation of non-tree structures
On the other hand, in some domains, non-tree structures might be allowed In that case, isTree() would still be a useful addition to a set of methods because it would tell you what kind of structure you were dealing with at any given time
UML for the New Approach The UML diagram on the overhead following the next one shows the Composite design pattern with new contents Components have id’s An isTree() method is also added The implementation of isTree() makes use of the Java Set class Keep in mind that one of the mathematical properties of a set (as opposed to a list) is that it shouldn’t contain duplicate elements
Notice also that the MachineComponent class shows two isTree() methods, one abstract and one concrete One takes a set parameter and one doesn’t, so they’re overloaded This turns out to be a useful technique for inheriting as much as possible out of the superclass How it works will be described after the diagram
How the Methods in the New Diagram Interact As noted earlier, the component class has two isTree() methods, one abstract and one concrete The abstract method takes a parameter The concrete method does not This concrete/abstract dichotomy is not necessarily an inherent aspect of the design pattern However, how these methods are declared and implemented in the classes of the pattern show a certain subtlety which is worth noting
Superficially, these declarations may seem confusing and potentially complex Understanding them requires an understanding of polymorphism and dynamic binding This combination of abstract and concrete methods has a beneficial effect It minimizes the number of different places where the same method signature has to be implemented in the set of classes
The basic idea underlying isTree()/isTree(s:Set) comes down to these two points: In order to tell whether something is a tree, you have to maintain a set of nodes and call a method that takes such a set of nodes as a parameter
However, from client code, for example, you would like to simply be able to call isTree() without having to pass a set parameter You would like the classes of the design pattern to take care of implementing versions which require a parameter (and are hidden from client code)
In the superclass, MachineComponent, there is an abstract method isTree(set:Set)—a version which takes a set parameter—which the subclasses have to implement In the superclass, MachineComponent, there is also a concrete method isTree()—a version which doesn’t take a parameter—which the subclasses simply inherit
The implementation of the concrete version of the isTree() method in MachineComponent is based on the abstract version The subclasses implement the abstract version concretely Polymorphism and dynamic binding mean that you can call the version without a parameter on something typed to component, and what happens depends on the implementation with a parameter for the actual object type in the subclass
The book refers to this as delegation This is how the book describes the relationship between the two versions of the method: The MachineComponent code delegates an isTree() call to its abstract isTree(s:Set) method In other words, the isTree() implementation contains a call to isTree(s:Set) The declaration/implementation of the two methods in the superclass is shown on the next overhead
Method Implementation and Declaration in MachineComponent public boolean isTree() { return isTree(new HashSet()); } protected abstract boolean isTree(Set s);
If the foregoing explanations weren’t sufficient, this is what should be on your mind There is no implementation of the abstract method What does it mean, then, to call the abstract method in the implementation of the concrete method? Is this allowed? If so, how does it work?
The isTree(set:Set) method is abstract—but for this reason, the class that contains it is abstract This means that there can be no instances of the abstract class As a consequence, the only real cases where the concrete method is called are when it is inherited and called on instances of the subclasses
At this point, it should be clear that the non-implementation of the abstract version of the method in the superclass is not a problem The subclasses are obligated to provide implementations of the abstract method
When the inherited concrete method is called on a subclass object, the version of the method that takes a parameter, which is called in the body of that method, will be the one defined in the subclass The fact that this scheme works is the result of polymorphism and dynamic binding
What is accomplished by this? It is possible to call isTree() without a parameter on things that are typed MachineComponent, namely objects that are either Machine and MachineComposite In other words, the desired interface for client code is isTree() without a parameter
Internally, isTree(set:Set) with a parameter is needed in order to support the logic of checking whether an object is a tree The subclasses only need to implement isTree(set:Set) They can then inherit the version without a parameter
In theory, in an application program you could directly call the versions that take a parameter That would mean providing a parameter In practice, the versions that take a parameter would only be called as a result of an initial call to the version that doesn’t take a parameter This is the version inherited from the superclass, where the implementation includes the construction of the needed set parameter
Method Implementations in the Subclasses In order to complete the design as outlined so far, it’s necessary to provide subclass implementations of the abstract isTree(set:Set) method that takes a set parameter The implementation for the simple Machine class is straightforward The implementation for the MachineComposite class will be recursive, and in the recursion the sets of visited nodes will be passed down by calling the version of the isTree(set:Set) method that takes a parameter
isTree(set:Set) in Machine The book provides this code for the simple Machine class protected boolean isTree(Set visited) { visited.add(this); return true; }
A simple machine by itself always satisfies the definition of a tree That accounts for the fact that the method always returns true A simple machine may also be part of a composite, and the call to isTree(s:Set) on this object may be the result of a recursion in the call to isTree(s:Set) on the composite that contains it
At that point the simple machine has to be added to the list of nodes visited That accounts for the first line of code in the method implementation, where “this” is added to the visited set Keep in mind that the parameter is a reference, so after returning from this call, the addition of the object is reflected in the set that belongs to the calling method
isTree(setSet) in MachineComposite The MachineComposite implementation of isTree(set:Set) is recursive It has to add the object it is called on to the visited set
Just to keep things straight, remember these two things The isTree(s:Set) method of MachineComposite deals with a set of visited node objects The MachineComposite class also contains a List of all its components You don’t want to confuse the visited set and the component list
The isTree(set:Set) method for MachineComposite iterates over the composite’s components In that iteration, isTree(set:Set) is called on each component
Otherwise, the method should return true The method should (will) return false under either of these two conditions: Either a component is encountered that is already in the visited set Or a recursive call to isTree(set:Set) on one of its elements returns false Otherwise, the method should return true
Challenge 5.5 Write the code for MachineComposite.isTree(Set visited) Solution 5.5 A reasonable implementation of MachineComposite.isTree() is [see next overhead]:
protected boolean isTree(Set visited) { visited.add(this); Iterator i = components.iterator(); while(i.hasNext()) MachineComponent c = (MachineComponent) i.next(); if(visited.contains(c) || !c.isTree(visited)) return false; } return true;
Another Example A UML diagram for another example is given on the following overhead It is based on cups and seeds It completely parallels the diagrams given so far There is just one diagram for this design pattern
Lasater’s UML Lasater’s UML diagram is given on the following overhead It completely parallels the book’s initial generic example It has the advantage that it includes a client It also shows in greater detail what methods might be desirable in a client It also has a comment which makes it clear that method implementations would be based on iteration
The End? The rest of the chapter is on the topic of composites with cycles If there is time, it will be covered in class Otherwise, it will be up to students to read the sections in the book and the remainder of these overheads on their own
Composites with Cycles The book at this point concedes that the previous example of a structure that was not a tree could be regarded as an accident Letting the mixer have two parents is a mistake to be avoided in modeling the situation However, there are problem domains where cycles may be a correct part of the model
The book returns to Oozinoz for an example This time the topic is modeling the manufacturing process rather than modeling the factory machines For the sake of background, the book describes some of the manufacturing process It also provides the following illustration of a fireworks shell
The following UML diagram shows how processes are modeled using the Composite design pattern At the top of the hierarchy is the ProcessComponent class Under it is the ProcessStep class A ProcessStep would be a single, discrete step in the manufacturing process
Under the ProcessComponent class is also the ProcessComposite class A component of a process may be an instance of ProcessComposite, which contains a List of constituent subprocesses The ProcessComposite class also has two subclasses, ProcessAlternation and ProcessSequence
The UML diagram by itself doesn’t reveal what will be the critical point of this discussion However, the explanation of the manufacturing process in the text reveals it After shells are manufactured, they are inspected If they do not pass quality control, they have to be reworked
In the Oozinoz world, you don’t just toss the ingredients back in the bin, you disassemble the shell, apparently saving as much completed work as possible Then you make the shell again This is the conceptual part of the Oozinoz model that introduces a cycle which is considered normal, not an aberration
Figure 5.8 is an incomplete diagram showing the manufacturing process as a sequence of objects with references to each other The book states that the reworkOrFinish subprocess takes one of two alternative paths, either disassembly followed by make, or finish It is the statement “disassembly followed by make” which reveals the potential cycle in the manufacturing process Manufacturing starts with make, and in the case of a defective shell, starts over again with make
Challenge 5.6 Figure 5.8 shows the objects in a model of the shell assembly process. A complete object diagram would show links between any objects that refer to each other. For example, the diagram shows the references that the make object retains. Your challenge is to fill in the missing links in the diagram
Solution 5.6 Your solution should show the links in Figure B.4 Comment mode on: Notice that the hard part, the cycle, was pretty much given away by the text The easier part was also essentially given away, but it might not have been quite as evident Some of the boxes have more than one arrow exiting from them, representing alternative outcomes and paths during the manufacturing process The sequence of boldface arrows highlights the cycle
In the earlier example the book worked with an isTree() method The implicit idea was that having a tree like structure was desirable The isTree() method would make it possible to detect when something was “wrong” Something wrong would cause the node count to be wrong, for example
Now the book wants to consider how to implement methods correctly if cycles are allowed in the structure Before considering the code example, it is helpful to look at the outline of the methods provided in the UML diagram given earlier
The getStepCount() method in this example is somewhat analogous to the getMachineCount() method in the previous example However, note that instead of counting all instances of steps, getStepCount() is intended to count only the distinct leaf nodes in a path Since cycles may be present, any given path may be “endless”, but even so, it will only contain a finite number of distinct leaf nodes
The implementation of getStepCount() is similar to getMachineCount in the overall approach to implementation There is a version which takes a Set parameter and a version which doesn’t The version that doesn’t works by delegation to the version that does
In other words, this would be part of the implementation of the component class: public int getStepCount() { return getStepCount(new HashSet()); } public abstract int getStepCount(Set visited);
The implementation of the version of the method that takes the parameter is simple for the single ProcessStep class It will be shown on the next overhead Note that it adds the name of the leaf to the list, not the object itself Once again, the reason for this that method isn’t supposed to count all of the objects in a path, but the distinct leaf nodes in a path Various process components may be repeated in a single manufacturing path If the repeated components have the same name, then the nodes they contain will not be counted again
public int getStepCount(Set visited) { visited.add(name); return 1; }
The version of the getStepCount(Set visited) method for the ProcessComposite class is recursive The getMachineCount() method for the MachineComposite class was recursive However, getMachineCount() method did not protect against cycles
The implementation of the getStepCount() method has to produce the correct result even if a cycle is present in the process composite First of all, this means that like the version of the method for a simple Process, this method will work with names of processes in the visited list It also means that the structure of the method will not be exactly the same as the structure of the getMachineCount() or isTree() methods
The code for the method is shown on the next overhead In addition to the differences noted already, the implementation is also different because it uses a for loop rather than iteration Presumably, this is done simply to show an alternative approach It does not appear that this is part of the logic of avoiding infinite looping/recursion
public int getStepCount(Set visited) { visited.add(getName()); int count = 0; for(int i = 0; i < subprocesses.size(); i++) ProcessComponent pc = (ProcessComponent) subprocess.get(i); if(!visited.contains(pc.getName())) count += pc.getStepCount(visited); } return count;
Honestly, this code is not especially easy to understand As usual, figuring out what’s happening with recursion takes some close study I am running out of explanations to put in these overheads
Consequences of Cycles If cycles are allowed in a structure, implementing methods will require making sure that you don’t “hit” the same node twice, depending on the exact purpose of the method Other operations will simply make no sense For a structure with a cycle, you can count the number of distinct leaf nodes in a path, for example However, you can’t compute the length of a path The path with a cycle is endless, and any method to do this computation will loop/recur endlessly It will also not be possible to implement any method which relies on the existence of a single parent for every node in the structure
Summary The Composite design pattern contains two concepts A group can contain individual items or groups of items The pattern makes sure that the individual items and the groups of items share a common interface This can be done by means of an abstract class or a Java interface
The Composite design pattern can easily lead to recursive method implementations If that is the case, then you can take steps to make sure that structures are always trees You can also take steps to make sure that method implementations will still work if non-tree structures are allowed
The End