whi*_*rra 6 java oop decorator composition marker-interfaces
我发现自己经常遇到以下问题.我有一些标记接口(为了简单起见,我们使用java.io.Serializable)和几个包装器(Adapter,Decorator,Proxy,...).但是当你将Serializable实例包装在另一个实例(不可序列化)中时,你会失去功能.java.util.RandomAccess也会出现同样的问题,可以通过List实现来实现.有没有一个很好的OOP方式来处理它?
这是最近关于番石榴邮件列表的讨论 - 我的答案触及了这个相当基本的问题.
http://groups.google.com/group/guava-discuss/browse_thread/thread/2d422600e7f87367/1e6c6a7b41c87aac
它的要点是: 当你期望你的对象被包装时,不要使用标记接口.(嗯,这很普遍 - 你怎么知道你的对象不会被客户端包裹?)
例如,一个ArrayList.它RandomAccess显然实现了.然后你决定为List对象创建一个包装器.哎呀!现在,当你包,你要检查包装的对象,如果是RandomAccess的,你创建包装应该也实现了RandomAccess!
这工作"很好"...如果你只有一个标记界面!但是如果被包装的对象可以是Serializable呢?如果它是"不可变"(假设你有一个类型来表示),该怎么办?还是同步?(用同样的假设).
正如我在对邮件列表的回答中所指出的那样,这种设计缺陷也体现在良好的旧java.io包装中.假设你有一个方法接受一个InputStream.你会直接读它吗?如果它是一个昂贵的流,并且没有人愿意BufferedInputStream为你包裹它怎么办?哦,这很容易!你只需检查stream instanceof BufferedInputStream,如果没有,你自己包装!但不是.流可能在链的某处缓冲,但您可能会获得它的包装,这不是BufferedInputStream的实例.因此,"此流缓冲"的信息将丢失(您可能会悲观地浪费内存来再次缓冲它).
如果要正确执行操作,只需将功能建模为对象即可.考虑:
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!
}
Run Code Online (Sandbox Code Playgroud)
编辑:应该注意我使用枚举只是为了方便.可以通过接口Capability和实现它的开放式对象集(可能是多个枚举).
因此,当您包装这些对象时,您将获得一组功能,并且您可以轻松决定要保留哪些功能,要删除哪些功能以及要添加哪些功能.
显然,这确实有它的缺点,所以它只能用于你真正感受到包装器隐藏功能表达为标记接口的痛苦的情况.例如,假设您编写了一段采用List的代码,但它必须是RandomAccess AND Serializable.通过常规方法,这很容易表达:
<T extends List<Integer> & RandomAccess & Serializable> void method(T list) { ... }
Run Code Online (Sandbox Code Playgroud)
但在我描述的方法中,你所能做的就是:
void method(YourType object) {
Preconditions.checkArgument(object.getCapabilities().contains(SERIALIZABLE));
Preconditions.checkArgument(object.getCapabilities().contains(RANDOM_ACCESS));
...
}
Run Code Online (Sandbox Code Playgroud)
我真的希望有一种比其中任何一种更令人满意的方法,但从前景来看,它似乎是不可行的(至少没有引起组合型爆炸).
编辑:另一个缺点是,如果没有每个功能的显式类型,我们就没有自然的位置来放置表达此功能提供的方法.这在讨论中并不太重要,因为我们讨论标记接口,即不通过其他方法表达的功能,但我提到它是完整的.
PS:顺便说一句,如果你浏览Guava的集合代码,你真的可以感受到这个问题引起的痛苦.是的,一些优秀的人试图隐藏在好的抽象背后,但潜在的问题仍然是痛苦的.
如果您感兴趣的接口都是标记接口,那么您可以让所有的包装类实现一个接口
public interface Wrapper {
boolean isWrapperFor(Class<?> iface);
}
Run Code Online (Sandbox Code Playgroud)
其实现如下:
public boolean isWrapperFor(Class<?> cls) {
if (wrappedObj instanceof Wrapper) {
return ((Wrapper)wrappedObj).isWrapperFor(cls);
}
return cls.isInstance(wrappedObj);
}
Run Code Online (Sandbox Code Playgroud)
这就是它的完成方式java.sql.Wrapper.如果界面不仅仅是一个标记,但实际上有一些功能,您可以添加一个方法来解包:
<T> T unwrap(java.lang.Class<T> cls)
Run Code Online (Sandbox Code Playgroud)