Is there a workaround for layout and marker interfaces?

I see that I regularly encounter the following problem. I have some kind of Marker interface (for simplicity, use java.io.Serializable ) and several wrappers (Adapter, Decorator, Proxy, ...). But when you terminate the Serializable instance in another instance (which is not serializable), you lose functionality. The same problem occurs with java.util.RandomAccess, which can be implemented by List implementations. Is there a good OOP way to handle it?

+6
java oop decorator composition marker-interfaces
source share
4 answers

Here's a recent discussion on the Guava mailing list - my answer addresses this rather fundamental issue.

http://groups.google.com/group/guava-discuss/browse_thread/thread/2d422600e7f87367/1e6c6a7b41c87aac

The bottom line is: Do not use marker interfaces when you expect your objects to be wrapped . (Well, this is pretty general - how do you know that your object will not be wrapped by the client?)

For example, ArrayList . It implements RandomAccess , obviously. Then you decided to create a wrapper for the List objects. Unfortunately! Now when you wrap, you should check the wrapped object, and if it is RandomAccess, the created shell should also implement RandomAccess!

This works fine ... if you only have one marker interface! But what if a wrapped object can be Serializable? What if it's, say, “Immutable” (if you have a type to denote this)? Or synchronously? (With the same assumption).

As I also point out in my response to the mailing list, this design java.io also evident in the old java.io package. Say you have a method that accepts an InputStream . Will you read right from it? What if it is an expensive stream and no one cared to wrap it in a BufferedInputStream for you? Oh it's easy! You just check the stream instanceof BufferedInputStream , and if not, you wrap it yourself! But no. A stream can buffer somewhere down the chain, but you can get its wrapper , which is not an instance of BufferedInputStream . Thus, the information that “this stream is buffered” is lost (and you must pessimistically destroy the memory in order to reload it again).

If you want to do something right , just model features 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 the enumeration for convenience only. There may be a Capability interface and an open set of objects that implement it (possibly several enumerations).

So, when you wrap an object from them, you get a set of features, and you can easily decide which features to keep that you need to remove to add.

This one , obviously, has its drawbacks, so it should be used only in cases where you really feel pain in the shells that hide the possibilities expressed as marker interfaces. For example, let's say you write a piece of code that takes a list, but it must 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 described above, 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 was a more satisfactory approach than any of them, but from the perspective it seems not feasible (without, at least, causing the explosion of a combinatorial type).

Edit: Another drawback is that without an explicit type for each opportunity, we do not have a natural place to place methods that express what this opportunity offers. This is not very important in this discussion, since we are talking about marker interfaces, that is, capabilities that are not expressed using additional methods, but I mentioned this for completeness.

PS: By the way, if you are looking at the code of the Guava collection, you really can feel the pain that this problem causes. Yes, some good people try to hide it behind beautiful abstractions, but the main problem is still painful.

+7
source share

If the interfaces you are interested in are all marker interfaces, you can use all wrapper classes for the interface

 public interface Wrapper { boolean isWrapperFor(Class<?> iface); } 

whose implementation will look like this:

 public boolean isWrapperFor(Class<?> cls) { if (wrappedObj instanceof Wrapper) { return ((Wrapper)wrappedObj).isWrapperFor(cls); } return cls.isInstance(wrappedObj); } 

Here's how to do it in java.sql.Wrapper . If the interface is not only a marker, but actually has some functions, you can add a method to expand:

 <T> T unwrap(java.lang.Class<T> cls) 
+5
source share

For the likes of RandomAccess you cannot do much. You can, of course, do an instanceof check and instantiate the corresponding class. The number of classes grows exponentially with the help of markers (although you can use java.lang.reflect.Proxy ), and your creation method should know about all the markers.

Serializable not so bad. If the indirectness class implements Serializable , then everything will be serialized if the target class is Serializable , and not if it is not.

+1
source share

There are several alternatives, although none of them are very pleasant.

  • Make the shell an implementation of the interface if it is known at compile time, if the wrapped object also implements the interface. The factory method can be used to create a wrapper if it is unknown before execution, if the wrapped object implements an interface. This means that you have separate wrapper classes for possible combinations of implemented interfaces. (With one interface, you need 2 wrappers, one with one and without it. For 2 interfaces, 4 wrappers, etc.)

  • Export wrapped objects from the wrapper so that clients can move around the chain and test each object in the chain for an interface using instanceof . This violates encapsulation.

  • Have a dedicated method for getting an interface implemented by both the wrapper and the wrapped object. For example. asSomeInterface() . The wrapper delegates the wrapped object or creates a proxy server around the wrapped object to preserve encapsulation.

  • Create one wrapper class for each interface — the wrapper is implemented as usual — it implements the interface and delegates another implementation of this interface. A wrapped object can implement several interfaces, so several wrapper instances are combined into one logical instance using a dynamic proxy to delegate the interface methods implemented by the proxy server to the corresponding shell instance. It is necessary that the set of interfaces implemented by the proxy server does not have common method signatures.

Microsoft has baked aggregation ( Wikipedia ) into their component object model (COM). It seems that most of them are not used by the majority, but at the same time it creates significant complexity for developers of COM objects, since there are rules that each object must comply with. Wrapped objects are encapsulated by the fact that wrapped objects know about their shells, you must maintain a pointer to the shell that is used in the implementation of QueryInterface (loosely instanceof ) for open public interfaces - the wrapped object returns an interface implemented on rather than its own implementation.

I have not seen a clean, easy to understand / implement, and properly encapsulated solution. COM aggregation works and provides full encapsulation, but these are the costs that you pay for each object you implement, even if it is never used together.

+1
source share