Best Practice: Commons Composite


The Commons Composite is a best practice that encompasses the adapter, proxy, composite, and state patterns. In essence, all of the encompassed patterns are based on having a conductor manage a number of classes as action items. The action items are typically logic classes or interfaces. The conductor is responsible for organizing and knowing when and how to call the action items. In a typical implementation a client using the Commons Composite will instantiate both the conductor and action items, but will interact with only the conductor.

In the simplest example of the Composite, there is only one structural class or interface, and one logic class or interface. A logic class or interface is a programming construct where the class or interface is primarily used to solve a programming logic that is related to the program problem. An example would be deciding whether or not to call a specific method on an interface, which is similar to the adapter pattern. Typically, the adapter pattern is used to fit an existing implementation with a different implementation that a consumer of the best practice expects.

The problem with the Composite variation of the best practice is that it implies fitting two classes together using a middle class. Doing things this way can be inefficient. Open Source projects like the Jakarta projects do not like to make an interface or implementation intended for one purpose fit into another purpose. Instead, the Jakarta group defines a new architecture that reflects the new approach. You can find the simplest example of this approach in the Jakarta Tomcat sources. In the sources there are three classes, Tomcat3Request , org.apache.coyote.tomcat4.CoyoteRequest , and org.apache.coyote.tomcat5.CoyoteRequest . Each class represents a request in that version of the application.

When new classes are used to define new implementations , it is a clean-slate approach where the old may or may not be kept and the new will be a better and potentially incompatible version. The result is that the new version of the software is the best it can be. The Jakarta Tomcat project can do this because it is an Open Source project not constrained by budgets , time, and current customers. In the for-profit world, constantly changing the interfaces can cause problems and tends to be avoided. However, the result is that the stagnant interfaces have to carry around a large amount of baggage. The Composite best practice attempts to be a compromise between the changing and stagnant interfaces. However, there is a warning: using too many composites makes for bad programming style and potentially buggy code.

The Basic Implementation of Composite

The essence of Composite is that there is some already existing functionality, which could be a class or an interface associated with an implementation. To keep things simple, Listing 2.12 uses a class.

Listing 2.12
start example
 class ClassToHide { public void hiddenStuff() { } } 
end example
 

In Listing 2.12, the class ClassToHide has a method hiddenStuff , which should be called, but not exposed, to the public world. Note that the class and method may already be public; therefore, hiding the class and method from a Java implementation point of view is not possible. The conductor considers this class the action item. The task of the conductor is to implement an interface and expose the action item using the implemented interface's methods . Most likely, the conductor is implemented as a Commons Bridge. Listing 2.13 shows an example of this implementation.

Listing 2.13
start example
 interface ExposeToAll { void method(); } final class ClassToExpose extends ClassToHide implements ExposeToAll { public void method() { hiddenStuff(); } } 
end example
 

In Listing 2.13, the interface ExposeToAll is what a client expects. The conductor is the class ClassToExpose , which subclasses the interface ExposeToAll and extends the action item class ClassToHide . Then, in the method method , a method call is made to the method hiddenStuff , which represents the already existing functionality. The implementation of the method method need not be only a single method call; it could contain more complicated logic. The idea is to delegate the new interface functionality to the original implementation.

Variation: Encapsulation

In Listing 2.13, the class ClassToExpose subclasses the class ClassToHide . While this works very well, ClassToHide may expose undesired functionality from the inheritance. An example of an undesirable side effect would be if the developer bought the action item as a component and then one day replaced the action with a new component. If a developer used the class ClassToExpose directly and not the interface ExposeToAll , then some code might break. While the developer may have done this intentionally or unintentionally, the damage is done. The solution is not to buy a specific component but to encapsulate the component instead. Using encapsulation, a developer cannot get direct access to the action item, as shown in Listing 2.14.

Listing 2.14
start example
 class VariationClassToExpose implements ExposeToAll { private ClassToHide _cls = new ClassToHide(); public void method() { _cls.hiddenStuff(); } } 
end example
 

In Listing 2.14, the class ClassToHide is a private data member of the class VariationClassToExpose . When the method method from the interface ExposeToAll is called, the private data member _cls is referenced to call the action item implementation.

Variation: Delegation

In the encapsulation variation of the Commons Composite, the client has no way to call methods exposed by the class ClassToHide . The Composite shown in the basic variation has the problem of exposing methods that should not be exposed, solved by the encapsulated variation. The third variation (called delegation of the Composite) is a hybrid between the basic and encapsulated variation. In the delegation variation, an interface exposes all of the methods like the basic variation, but the original implementation is referenced using the encapsulation variation.

The encapsulation variation of the Commons Composite, for reference purposes, is very similar to the proxy pattern. The only difference between them is intent. A sample delegation variation is shown in Listing 2.15.

Listing 2.15
start example
 package com.devspace.jseng.granularization; interface SomeInterface { void method(); } class OriginalImplementation implements SomeInterface { public void method() { } } class Proxy implements SomeInterface { OriginalImplementation _cls = new  OriginalImplementation(); public void method() { _cls.method(); } } 
end example
 

In Listing 2.15, the class Proxy implements the interface SomeInterface , making the delegation variation similar to the basic variation. In the implementation of the Proxy class is the reference to the class OriginalImplementation , which makes the delegation variation similar to the encapsulation variation. The two variations combined make up the delegation variation. The advantage of this approach is that a client can take advantage of a new functionality without having to completely rewrite the old functionality. This approach is very good when you need to create implementations that include service pack fix-ups or specific implementation details specific to a client.

Variation: Many Action Items

This variation is used extensively throughout the Jakarta sources. The main difference between this variation and the other Composite variations is that the conductor manages multiple action items instead of only a single action item. The conductor in this variation takes on the role of managing the collection of action items.

In this variation of the Commons Composite, the objective is to be able to group together various components that support a common class or interface into a collection and then perform an operation on the collection. A simple example of this variation of Commons Composite is a file system management tool. In a typical file system, there is a file and a directory entry. Both could be represented as an interface that has an identifier and some type of content. Some type of collection system would manage the files and directories. If a search were be performed, it would be necessary only to query the collections, iterate through the various components, and query the identifier of each component. The conductor would manage and query the individual components on behalf of the client. The advantage of this approach is that the user does not need to understand the individual functionality of the action items. The conductor will manage all of the details.

In the Jakarta sources, the word "composite" is used, but so are other words such as "pipeline" and "chain." All of these words are examples of the many action item variation. The simplest and best example of the many action item variation can be found in the Jakarta Tomcat project, mentioned earlier. In the Tomcat project, requests are processed using a Pipeline and Valve, as illustrated in Listing 2.16.

Note

Please note that some readers may comment that the Jakarta Cocoon or Jakarta Ant projects are very good examples of a composite. That is absolutely correct, but both of those projects include other aspects in their composite that are beyond the scope of this book. Discussing these would confuse you more than help you.

Listing 2.16
start example
 package org.apache.catalina; public interface Valve { public String getInfo(); public void invoke(Request request, Response response,  ValveContext context) throws IOException, ServletException; } package org.apache.catalina; public interface Pipeline { public Valve getBasic(); public void setBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void invoke(Request request, Response response) throws IOException, ServletException; public void removeValve(Valve valve); } 
end example
 

In Listing 2.16, the interface Pipeline includes a series of methods that manipulate the interface Valve . The objective of the interface Pipeline is to create a collection of valves that are then processed when the interface pipeline method invoke is called. The interface Pipeline is the conductor. In Listing 2.16, for the interface Pipeline there would most likely be a structural class that manages the details of the action items. For the interface Pipeline , the structural methods are setBasic , getBasic , addValve , removeValve , and getValves . On the same interface, the method invoke is a logic method that manipulates the managed valves. What will typically happen is the user class that subclasses the abstract structural class (which subclasses the interface Pipeline) will execute some method on the interface Valve . What has been ignored is how the classes that implement the interfaces Pipeline and Valve are instantiated . Each interface would be implemented using a Commons Bridge, so the instantiation would be managed using those best practices. A typical implementation would be Listing 2.17.

Listing 2.17
start example
 package com.devspace.jseng.granularization; import org.apache.catalina.Pipeline; import org.apache.catalina.Valve; import org.apache.catalina.Request; import org.apache.catalina.Response; import org.apache.catalina.ValveContext; import java.io.IOException; import javax.servlet.ServletException; class MyLogic implements Valve { public String getInfo() { return ""; } public void invoke(Request request, Response response,  ValveContext context) throws  IOException,ServletException { } } abstract class DefaultStructural implements Pipeline { public void setBasic(Valve valve) { } public Valve getBasic() { return null; } public void removeValve(Valve valve) { } public void addValve(Valve valve) { } public Valve[] getValves() { return null; } public abstract void invoke(Request request, Response response) throws IOException,ServletException; } class User extends DefaultStructural { public void invoke(Request request, Response response) throws IOException,ServletException { } } 
end example
 

In Listing 2.17, the class MyLogic subclasses the interface Valve and represents a logic implementation of the interface. In a typical implementation, there would be multiple different implementations of the interface Valve . The class DefaultStructural is an abstract class implementation of the interface Pipeline and represents the structural class. The methods that are implemented in the abstract class all relate to structural methods that manage the collection of valves. There is also another method, invoke , which is abstract. It is added only for notation purposes. It explicitly defines that any class that extends the class DefaultStructural must also implement the method invoke .

Variation: Related Action Items

In the design pattern community, there is debate on whether the conductor should be a separate interface or a class. The answer is not either one; rather, it hinges on whether the action item and conductor should be related.

In a composite, a related action item and conductor is when the consumer cannot easily distinguish between the two. In the delegation variation, the consumer did not know if he was using the original class or a proxy class delegating to the original class. In the basic variation or many action item variation, there was a distinction between the two classes. The related action item variation is a hybrid of the many action items and the delegation variations. An example of the related action item variation is shown in the XML axis project. In the XML Axis project, which is a SOAP toolkit, a related action item variation is created by defining the interface and then having both the conductor and action item derive from the same interface, as shown in Listing 2.18.

Listing 2.18
start example
 package org.apache.axis ; public interface Handler extends Serializable { public void init(); public void cleanup(); public void invoke(MessageContext msgContext) throws AxisFault ; public void onFault(MessageContext msgContext); public boolean canHandleBlock(QName qname); public List getUnderstoodHeaders(); public void setOption(String name, Object value); public Object getOption(String name); public void setName(String name); public String getName(); public Hashtable getOptions(); public void setOptions(Hashtable opts); public Element getDeploymentData(Document doc); public void generateWSDL(MessageContext msgContext) throws AxisFault; }; package org.apache.axis ; public interface Chain extends Handler { public void addHandler(Handler handler); public boolean contains(Handler handler); public Handler[] getHandlers(); }; 
end example
 

The conductor is the interface Chain , but notice how the interface Chain subclasses the interface Handler , which is the action item interface. The reason for doing this is to make logic interfaces self-reliant. Consider it from the scope of the client. Instead of having to deal with two interfaces, where one is the conductor and the other the action item, you have only one interface. Therefore, when calling the action method, the consumer would call either an action item or a conductor that calls the action items managed by the conductor. This approach allows a system to dynamically configure itself without having to indicate to the consumer that the configuration has changed.

Some Smaller Details

The Commons Composite best practice is used extensively throughout the Jakarta sources. The variations discussed earlier in this chapter generally use only interfaces. However, in the Jakarta sources the conductor may very often be a class, not an interface. Granted, the action item is always an interface. Whether or not that the conductor is an implementation or an interface depends entirely on the context. For example, if the conductor is defined locally and used within a package or subsystem, then using an interface is tedious . The rule on when to use a class and when to use an interface depends on the scope of the conductor. If the composite is used globally, as is shown in Listing 2.16, then an interface is the only possible solution. However, if the composite is used only within the local package or subsystem, a class definition of the composite is correct. An example of a class-based conductor approach is Listing 2.19.

Listing 2.19
start example
 package org.apache.commons.configuration; public interface Configuration { Configuration subset(String prefix); boolean isEmpty(); boolean containsKey(String key); // Other methods... } public class BaseConfiguration implements Configuration { // methods... } public class CompositeConfiguration implements Configuration {  // methods... } 
end example
 

In Listing 2.19, the action item is the interface Configuration . The conductor is the class CompositeConfiguration . The class CompositeConfiguration contains methods to add and remove implementations based on the interface Configuration . The class Base- Configuration is a structural class for the interface Configuration because the interface Configuration is very extensive and because it's tedious if you have to constantly implement all of the methods. Therefore, for the logic interface, it's a good idea to use a structural class. Remember that the structural class BaseConfiguration should be an abstract class.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net