Presentation is loading. Please wait.

Presentation is loading. Please wait.

Design Patterns in Java Chapter 5 Composite Summary prepared by Kirk Scott 1.

Similar presentations


Presentation on theme: "Design Patterns in Java Chapter 5 Composite Summary prepared by Kirk Scott 1."— Presentation transcript:

1 Design Patterns in Java Chapter 5 Composite Summary prepared by Kirk Scott 1

2 Composites Before defining a composite, let a component be defined A component is a class or interface which can be – Implemented by a class representing individual items – Or implemented by a class that represents a collection of such items 2

3 A composite is a class that contains a collection of components as defined above This means that the composite can contain both individual items and collections of such items The striking thing that will become apparent about the pattern is that the composite class implements the component interface A composite can contain both individual items and other instances of the composite class 3

4 The authors use the terms leaf and group to generically describe these concepts The use of the term leaf suggests that in at least some cases this design pattern results in tree-like groups of items It is important to note that in this design pattern, so-called leaf items are not restricted to the outermost branches of the tree 4

5 Take a directory structure for example Any directory can contain individual files as well as additional directories This ability to have elements that are either individual or composite is one of the important design features of the pattern 5

6 The second important design feature is to define behaviors (actions, operations, methods) which can be applied to both individual items and groups As will be shown shortly, a component class, a leaf class, and a composite class are integral parts of the Composite design pattern It is not the case that the composite class itself embodies the pattern Without all three classes you don’t have the Composite design pattern 6

7 As defined by the book, this is the intent of this design pattern: The intent of the Composite pattern is to let clients treat individual objects and compositions of objects uniformly 7

8 Notice the appearance of the word client in this definition As usual, the design pattern doesn’t exist in isolation The design pattern provides a model for making certain capabilities available to user written code The following UML diagram illustrates the structure of the Composite design pattern 8

9 9

10 At the top of the design pattern is a Component In the diagram and the following discussion, Component is implemented as an abstract class, which is perfectly all right The Component class could also be implemented as an interface In this example there is an advantage to implementing it as an abstract class It’s possible to provide some actual method implementations in the abstract class, along with the abstract method specifications 10

11 Either way, the purpose the Component class serves is to provide a generic interface that each of the classes underneath it in the design, the Leaf class and the Composite class, have to conform to As stated by the book, 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 11

12 Under the Component are subclasses Leaf and Composite which share the uniform interface A leaf is a leaf and there are no further details there except for overriding inherited methods or implementing abstract or interface methods The relationship between the Composite class and the Component class is more complex and gives this design pattern its important characteristics 12

13 In the UML diagram the composition symbol is used to signify that an instance of Composite can have instances of Component A component can be either a leaf or a composite, so a composite can have either leaves or composites or both 13

14 Notice the appearance, once again, of the concepts of polymorphism and interfaces in this design pattern An instance of Composite can have more than one different kind of thing What it can have can be defined by an abstract class or an interface 14

15 If what the composite can have is defined by an abstract class, then the composite can have anything that is a subclass of that class If what the composite can have is defined by an interface, then the composite can have anything that implements that interface 15

16 The design pattern can be summarized by two observations stemming from the foregoing Some kind of leaf class extends/implements the component class/interface which a composite can have More importantly, the composite class itself extends/implements the component class/interface which the composite can have The UML diagram is repeated below for reference 16

17 17

18 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? 18

19 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. 19

20 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. Such definitions are much more flexible than defining a composite to be a collection of leaves. 20

21 Solution 5.1, cont’d. If we allowed only collections of leaves, our “composites” could be only one layer deep. Commentary 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? 21

22 Commentary mode cont’d. Although I’m not suggesting trying to implement or model a directory structure using the Composite design pattern, I still think it’s the most succinct and familiar example of the pattern The pattern also arises in manufacturing (databases) where assemblies consist of parts and subassemblies 22

23 Recursive Behavior in Composites 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 23

24 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 24

25 The concrete example is given in the following UML diagram Note that the diagram includes a method, getMachineCount() As always, the devil is in the details Once you come up with a design pattern at the macro level, the question becomes, how do you implement the methods it contains? 25

26 26

27 Note that in the diagram the superclass, MachineComponent, is shown in italics In UML this means that MachineComponent is an abstact class The method getMachineCount() is also in italics and abstract Naturally, the subclasses have to implement this abstract method 27

28 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 28

29 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 29

30 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, calling the method will cause the leaf version of the method to be used If an element of the list is a composite, calling the method will cause the composite version of the method to be used 30

31 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 Depending on whether an element is a Machine or a MachineComposite, polymorphism and dynamic binding will determine which version of the method is called 31

32 Challenge 5.2 Write the code for the getMachineCount() methods implemented by Machine and MachineComposite 32

33 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 33

34 Solution 5.2 For the Machine class, getMachineCount() should be something like: public int getMachineCount() { return 1; } 34

35 The class diagram shows that MachineComposite uses a List object to track its components. To count the machines in a composite, you might write: 35

36 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]. 36

37 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 37

38 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 38

39 39

40 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 the challenge 5.3 in the form of the table on the next overhead 40

41 41

42 Solution 5.3 See the next overhead 42

43 43

44 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 44

45 At the very least, including 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 45

46 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 46

47 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 next page 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 47

48 48

49 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 The term cycle refers to a sequence of references where a given node appears twice Any data structure that contains a cycle can’t be a tree 49

50 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 50

51 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; } 51

52 The next overhead shows the UML diagram of the relationships between the objects created by the foregoing code 52

53 53

54 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 54

55 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 55

56 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 56

57 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 57

58 public int getMachineCount() { return 1; } 58

59 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 59

60 public int getMachineCount() { int count = 0; Iterator i = components.iterator(); while(i.hasNext()) { MachineComponent mc = (MachineComponent) i.next(); count += mc.getMachineCount(); } return count; } 60

61 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? 61

62 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()); } 62

63 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 63

64 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 64

65 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 65

66 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 If it had contained 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 66

67 The book pursues the idea of dealing with non-tree structures in this way It states that the application that allows structures to be created should check whether a node already exists in some component before allowing it to be added to the structure The book also states that a simple way to do this is to maintain a set of existing nodes 67

68 The book introduces a method named isTree() isTree() traverses a data structure, checking see whether any node is encountered more than once If so, then isTree() returns false 68

69 The idea is that if you wanted to, you could include calls to isTree() in an 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 69

70 The following UML diagram shows the Composite design pattern with an isTree() method added to it Notice that the suggested implementation makes use of the Java Set class Notice also that the MachineComponent class shows two isTree() methods, one abstract and one concrete How this works will be described in greater detail after the diagram 70

71 71

72 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 72

73 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 73

74 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 passes such a set of nodes 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) 74

75 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 75

76 The implementation of the concrete version of the isTree() method in MachineComponent is based on the abstract version 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 76

77 public boolean isTree() { return isTree(new HashSet()); } protected abstract boolean isTree(Set s); 77

78 The question that might (or should) be on your mind is this There is no implementation of 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? It is allowed and it does work The explanation of how it works follows 78

79 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 79

80 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 80

81 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 81

82 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 82

83 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 83

84 To reach the crux of the matter, 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 84

85 The book provides this code for the simple Machine class protected boolean isTree(Set visited) { visited.add(this); return true; } 85

86 A simple machine by itself always satisfies the definition of a tree That accounts for the fact that the method always returns true 86

87 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 87

88 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 88

89 The isTree(set:Set) method for MachineComposite iterates over the composite’s components In that iteration, isTree(set:Set) is called on each component 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 89

90 Challenge 5.5 Write the code for MachineComposite.isTree(Set visited) Solution 5.5 A reasonable implementation of MachineComposite.isTree() is [see next overhead]: 90

91 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; } 91

92 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 92

93 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 93

94 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 94

95 95

96 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 96

97 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 97

98 98

99 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 99

100 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 100

101 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 101

102 102

103 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 103

104 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 104

105 105

106 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 106

107 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 107

108 108

109 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 109

110 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 110

111 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); 111

112 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 112

113 public int getStepCount(Set visited) { visited.add(name); return 1; } 113

114 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 114

115 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 115

116 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 116

117 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; } 117

118 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 118

119 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 119

120 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 120

121 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 121

122 The End 122


Download ppt "Design Patterns in Java Chapter 5 Composite Summary prepared by Kirk Scott 1."

Similar presentations


Ads by Google