Objective: design a menu system that supports: a menu bar, pull-down menus, cascade menu buttons, and menu items (each of which be a cascade menu button). |
Objective: design a row-column layout manager widget capable of performing geometry management of other widgets (each of which could be a row-column layout manager). |
Objective: design a file system that supports directories, logical links, and files (each of which could be a directory). |
Before | After | |
---|---|---|
class File { public File( String name ) { m_name = name; } public void ls() { System.out.println( Composite.g_indent + m_name ); } private String m_name; } class Directory { public Directory( String name ) { m_name = name; } public void add( Object obj ) { m_files.add( obj ); } public void ls() { System.out.println( Composite.g_indent + m_name ); Composite.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { Object obj = m_files.get(i); // ***** Recover the type of this object ****** if (obj.getClass().getName().equals( "Directory" )) ((Directory) obj).ls(); else ((File) obj).ls(); } Composite.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add( two ); one.add( b ); two.add( c ); two.add( d ); two.add( thr ); thr.add( e ); one.ls(); } } // dir111 // a // dir222 // c // d // dir333 // e // b | // ***** Define a "lowest common denominator" ***** interface AbstractFile { public void ls(); } // ***** File implements the "lowest common denominator" class File implements AbstractFile { public File( String name ) { m_name = name; } public void ls() { System.out.println( CompositeDemo.g_indent + m_name ); } private String m_name; } // ***** Directory implements the "lowest common denominator" class Directory implements AbstractFile { public Directory( String name ) { m_name = name; } public void add( Object obj ) { m_files.add( obj ); } public void ls() { System.out.println( CompositeDemo.g_indent + m_name ); CompositeDemo.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { // ***** Leverage the "lowest common denominator" AbstractFile obj = (AbstractFile) m_files.get(i); obj.ls(); } CompositeDemo.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add( two ); one.add( b ); two.add( c ); two.add( d ); two.add( thr ); thr.add( e ); one.ls(); } } |
Composite can be traversed with Iterator. Visitor can apply an operation over a Composite. Composite could use Chain of Responsibility to let components access global properties through their parent. It could also use Decorator to override these properties on parts of the composition. It could use Observer to tie one object structure to another and State to let a component change its behavior as its state changes. [GoF, pp173,349]
Composite can let you compose a Mediator out of smaller pieces through recursive composition. [Vlissides, Apr96, p18]
Decorator is designed to let you add responsibilities to objects without subclassing. Composite's focus is not on embellishment but on representation. These intents are distinct but complementary. Consequently, Composite and Decorator are often used in concert. [GoF, p220]
Flyweight is often combined with Composite to implement shared leaf nodes. [GoF, p206]
Being able to treat a heterogeneous collection of objects atomically (or transparently) requires that the "child management" interface be defined at the root of the Composite class hierarchy (the abstract Component class). However, this choice costs you safety, because clients may try to do meaningless things like add and remove objects from leaf objects. On the other hand, if you "design for safety", the child management interface is declared in the Composite class, and you lose transparency because leaves and Composites now have different interfaces. [Bill Burcham]
Smalltalk implementations of the Composite pattern usually do not have the interface for managing the components in the Component interface, but in the Composite interface. C++ implementations tend to put it in the Component interface. This is an extremely interesting fact, and one that I often ponder. I can offer theories to explain it, but nobody knows for sure why it is true. [Ralph Johnson]
My Component classes do not know that Composites exist. They provide no help for navigating Composites, nor any help for altering the contents of a Composite. This is because I would like the base class (and all its derivatives) to be reusable in contexts that do not require Composites. When given a base class pointer, if I absolutely need to know whether or not it is a Composite, I will use dynamic_cast to figure this out. In those cases where dynamic_cast is too expensive, I will use a Visitor. [Robert Martin]
Common complaint: "if I push the Composite interface down into the Composite class, how am I going to enumerate (i.e. traverse) a complex structure?" My answer is that when I have behaviors which apply to hierarchies like the one presented in the Composite pattern, I typically use Visitor, so enumeration isn't a problem - the Visitor knows in each case, exactly what kind of object it's dealing with. The Visitor doesn't need every object to provide an enumeration interface. [Bill Burcham]
Composite doesn't force you to treat all Components as Composites. It merely tells you to put all operations that you want to treat "uniformly" in the Component class. If add, remove, and similar operations cannot, or must not, be treated uniformly, then do not put them in the Component base class. Remember, by the way, that each pattern's structure diagram doesn't define the pattern; it merely depicts what in our experience is a common realization thereof. Just because Composite's structure diagram shows child management operations in the Component base class doesn't mean all implementations of the pattern must do the same. [John Vlissides]