I see myself regularly confronted with the following problem. I have some kind of Marker Interface (for simplicity let's use java.io.Serializable
) and several wrappers (Adapter, Decorator, Proxy, ...). But when you wrap a Serializable instance in another instance (which is not serializable) you loose functionality. The same problem occurs with java.util.RandomAccess which can be implemented by List implementations. Is there a nice OOP way to handle it?
Here is a recent discussion on Guava mailing list - my answer touches upon this, rather fundamental issue.
http://groups.google.com/group/guava-discuss/browse_thread/thread/2d422600e7f87367/1e6c6a7b41c87aac
The gist of it is this: Don't use marker interfaces when you expect your objects to be wrapped. (Well, that's pretty general - how do you know that your object isn't going to be wrapped by a client?)
For example, an ArrayList
. It implements RandomAccess
, obviously. Then you decide to create a wrapper for List
objects. Oops! Now when you wrap, you have to check the wrapped object, and if it is RandomAccess, the wrapper you create should also implement RandomAccess!
This works "fine"...if you only have a single marker interface! But what if the wrapped object can be Serializable? What if it is, say, "Immutable" (assuming you have a type to denote that)? Or synchronous? (With the same assumption).
As I also note in my answer to the mailing list, this design deficiency also manifest itself in the good old java.io
package. Say you have a method accepting an InputStream
. Will you read directly from it? What if it is a costly stream, and nobody cared to wrap it in a BufferedInputStream
for you? Oh, that's easy! You just check stream instanceof BufferedInputStream
, and if not, you wrap it yourself! But no. The stream might have buffering somewhere down the chain, but you may get a wrapper of it, that is not an instance of BufferedInputStream. Thus, the information that "this stream is buffered" is lost (and you have to pessimistically waste memory to buffer it again, perhaps).
If you want to do things properly, just model the capabilities as objects. Consider:
interface YourType {
Set<Capability> myCapabilities();
}
enum Capability {
SERIALIAZABLE,
SYNCHRONOUS,
IMMUTABLE,
BUFFERED //whatever - hey, this is just an example,
//don't throw everything in of course!
}
Edit: It should be noted that I use an enum just for convenience. There could by an interface Capability
and an open-ended set of objects implementing it (perhaps multiple enums).
So when you wrap an object of these, you get a Set of capabilities, and you can easily decide which capabilities to retain, which to remove, which to add.
This does, obviously, have its shortcomings, so it is to be used only in cases where you really feel the pain of wrappers hiding capabilities expressed as marker interfaces. For example, say you write a piece of code that takes a List, but it has to be RandomAccess AND Serializable. With the usual approach, this is easy to express:
<T extends List<Integer> & RandomAccess & Serializable> void method(T list) { ... }
But in the approach I describe, all you can do is:
void method(YourType object) {
Preconditions.checkArgument(object.getCapabilities().contains(SERIALIZABLE));
Preconditions.checkArgument(object.getCapabilities().contains(RANDOM_ACCESS));
...
}
I really wish there were a more satisfying approach than either, but from the outlook, it seems not doable (without, at least, causing a combinatorial type explosion).
Edit: Another shortcoming is that, without an explicit type per capability, we don't have the natural place to put methods that express what this capability offers. This is not too important in this discussion since we talk about marker interfaces, i.e. capabilities that are not expressed through additional methods, but I mention it for completeness.
PS: by the way, if you skim through Guava's collections code, you can really feel the pain that this problem is causing. Yes, some good people are trying to hide it behind nice abstractions, but the underlying issue is painful nonetheless.
If the interfaces you're interested in are all marker interfaces, you could have all your wrapper classes implement an interface
public interface Wrapper {
boolean isWrapperFor(Class<?> iface);
}
whose implementation would look like this:
public boolean isWrapperFor(Class<?> cls) {
if (wrappedObj instanceof Wrapper) {
return ((Wrapper)wrappedObj).isWrapperFor(cls);
}
return cls.isInstance(wrappedObj);
}
This is how it's done in java.sql.Wrapper
. If the interface is not just a marker, but actually has some functionality, you can add a method to unwrap:
<T> T unwrap(java.lang.Class<T> cls)
For the likes of RandomAccess
there is not much you can do. You can, of course, do an instanceof
check and create an instance of the relevant class. The number of classes grows exponentially with markers (although you could use java.lang.reflect.Proxy
) and your creation method needs to know about all markers ever.
Serializable
isn't so bad. If the indirection class implements Serializable
then the whole will be serialisable if the target class is Serializable
and not if it isn't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With