Tuesday, April 23, 2013

Extend interface implementation (proxy/delegation pattern)


Extend interface implementation (proxy/delegation pattern)
Yes this is not a type mistake, I want to extend the implementation of an interface. How many times do we have access to an interface but not the implementation? In the case that we have the implementation we can usually inherit the class and override the public/protected functionality. But what do you do when there is a factory builder that returns an instantiated object with an interface and you do not know or what to know the implementation (since it can always change), and you still want to override some of the functionality.

Let’s take a simple example. To write an xml file you can use the following code:

Object request;
JAXBContext jc = JAXBContext.newInstance(classesToBeBound);
Marshaller m = jc.createMarshaller();
XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
ByteArrayOutputStream ba = new ByteArrayOutputStream();
XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(ba);  
m.marshal(request, writer);
 
This code takes a buffer array and marshals it to an xml document using the XMLStreamWriter.
The XMLStreamWriter is an interface:
public abstract interface javax.xml.stream.XMLStreamWriter

with function for writing xml documents like: writeStartElement, writeEndElement.
As we know there are characters in xml that cannot be written to the string document since they are used by the xml format. The characters are: [&<>'\”].  To overcome this when writing the xml for example, the > will be changed to &gt;. There are some systems that do not know how to read this string. For these systems there is the CDATA format (see http://xml.silmaril.ie/specials.html). A CDATA section is a string that starts with <![CDATA[ and ends with ]]>. Within this string you can write any text you want, for example:
<![CDATA[<sender>John Smith</sender>]]>

This is a legal text that will not be read as a xml mockup, but will be read as: &lt;sender&gt;John Smith&lt;/sender&gt;

Back to our case. Let’s say that we want to convert some of the text to CDATA but the XMLStreamWriter does not support this. We would like to override the function of writeCharacters. So how do we override an interface implementation? There are two way that are more or less the same, but one is more explicit and the other generic.
We will start with the specific solution. An interface is a contract that specifies an api without the implementation. The factory builder returns the interface with a specific implementation behind it. What we do is create a new class that holds a reference to the current instance and implements the interface. The all methods are delegated to the internal reference, and those methods that we want to override we can. For example (not a full class implementation):

public class DelegatingXMLStreamWriter implements XMLStreamWriter {
    XMLStreamWriter delegate;
   
    public DelegatingXMLStreamWriter(XMLStreamWriter del) {
        delegate = del;
    }
   
    public void close() throws XMLStreamException {
        delegate.close();
    }

    public void flush() throws XMLStreamException {
        delegate.flush();
    }

    public void writeCharacters(char[] arg0, int arg1, int arg2) throws XMLStreamException {
        delegate.writeCharacters(arg0, arg1, arg2);
    }
}

As you can see all that the class does is to delegate all functionally to the instance that was created by the factory, and specific implementations can be override (This class is a helper class that can be found at http://java.net/projects/stax-utils/pages/Home).

The second way to do this is not to explicitly write a class that delegates all the methods (they can be tens of methods), but to create a proxy object and the catch only the one method that needs to be overridden:
XMLStreamWriter proxyXMLStreamWriter = (XMLStreamWriter)Proxy.newProxyInstance(XMLStreamWriter.class.getClassLoader(),new Class[] { XMLStreamWriter.class },new InvocationHandler() {
      
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) {
if(method.getName().equals("writeCharacters") {
              // override specific method with functionality

}
});
}
Summary:
As you can see it is fairly easy to add functionality even to a class that you do not have the source code for. This is done using the proxy/delegation pattern.



No comments:

Post a Comment