我正在寻找装饰器模式的替代方案,使其更具动态性。作为一个简单的例子,假设我们有这样的代码:
interface Resource {
public String getName();
}
interface Wrapper extends Resource {
public Resource getSource();
}
interface Readable extends Resource {
public InputStream getInputStream();
}
interface Listable extends Resource {
public List<Resource> getChildren();
}
class File implements Readable {
...
}
class Zip implements Listable, Wrapper {
public Zip(Readable source) { ... }
}
可以看到,Zip不直接实现Readable,但是它读取的资源实现了Readable。假设我们构造一个zip文件:
Zip zip = new Zip(new File());
我不想(也不能)堆叠所有的接口来相互扩展(例如Listable扩展Readable),我也不能构造所有的对象来实现所有的功能,因为不是所有的功能都是相互关联的,你希望能够通过包装它们来"装饰"对象。
我相信这是一个常见的问题,但是有一个模式来解决它吗?使用"Wrapper"接口,你当然可以探测资源链来检查特性,但我不确定这是否是一个合理的方法。
问题如上所述,并不是所有的功能都是相关的,所以你不能建立一个很好的接口层次结构。例如,假设您有这个任意的新特性:
interface Rateable extends Resource {
public int getRating();
}
class DatabaseRateable implements Rateable, Wrapper {
public DatabaseRateable(Resource resource) { ... }
}
如果你运行:
Resource resource = new DatabaseRateable(new Zip(new File));
结果资源已经"丢失"了所有添加的特性(可读的,可列出的,…)。让ratable扩展说Listable是荒谬的。
我可以再次递归地检查resource.getSource()并找出所有的特征。在即时回复中,没有明确的解决方案,所以也许递归检查毕竟是一个好选择?
我想你要找的是mixins
链接的维基百科页面有一个很好的支持它们的OOP语言列表。或者您是专门与Java绑定的?
在我看来,您在这里所追求的概念是duck typing,这是Java本身不支持的(请参阅我关于基于反射的Java库的评论)。但是,在JVM上运行的其他语言肯定会这样做。例如- groovy:
class Duck {
quack() { println "I am a Duck" }
}
class Frog {
quack() { println "I am a Frog" }
}
quackers = [ new Duck(), new Frog() ]
for (q in quackers) {
q.quack()
}
您可以用groovy编写代码,并让它与其他Java代码无缝地一起工作,并在groovy中解决这个问题。
也许适配器模式可以提供帮助:
Readable r = zip.adapt( Readable.class );
要求方法adapt()
返回zip
的实例,该实例实现了Readable
接口。
实现通常使用一个"适配器管理器",它知道如何为所有注册的类型构建包装器。
这里可能不太合适,但是一个动态特征发现:
public class Features {
public <T> lookup(Class<T> intface)
throws UnsupportedOperationException {
return lookup(intface, intface.getSimpleName());
}
public <T> lookup(Class<T> intface, String name)
throws UnsupportedOperationException {
return map.get(...);
}
}
public class X {
public final Features FEATURES = new Features();
...
}
X x;
Readable r = x.FEATURES.lookup(Readable.class);
在修饰对象时,通常只修饰对象的一个接口,以修改或添加其行为的一个方面。您可以用不同的装饰器装饰对象的另一个接口。这些装饰器可以同时存在。
你可以把一个装饰器传递给一个方法,另一个传递给另一个。
当你想先用几个装饰器装饰对象,然后在你的代码中传递对象时,这会变得很粗糙。
因此,对于您的情况,我建议您将装饰器再次包装到一个对象中,该对象知道资源存在哪种装饰器。
class Resource {
private Readable readable;
private Listable listable;
private Rateable rateable;
setReadable(Readable readable) {
this.readable = readable;
}
setListable(Listable listable) {
this.listable = listable;
}
setRateable(Rateable rateable) {
this.rateable = rateable;
}
public boolean isRateable(){
return rateable != null;
}
public Rateable getRateable(){
return rateable;
}
// etc
}
File file1 = new File();
Resource resource = new Resource(file1);
resource.setReadable(new ReadableFile(file1));
resource.setListable(new ListableFile(file1));
resource.setRateable(new DatabaseRateableFile(file1));
然后你可以传递资源,它的用户可以发现这个特定的资源具有哪些特性。
Qi4j框架允许您以一种更简洁的方式(使用注释)完成此操作(以及更多)。您将Fragments组合成composite。不过,这确实需要一些时间来适应。为参考资料提供您自己的特定实现的好处是,它将更容易向其他人解释。
我将提供我自己的建议(如原问题所示)作为答案。如果有足够多的人认为这是一个有价值的解决方案,或者没有更好的解决方案,我将接受它。
简而言之,我自己的解决方案包括使用Wrapper接口向后遍历资源以找出存在哪些特性。给定示例:
Resource resource = new DatabaseRateable(new Zip(new File));
你可以想象这样做:
public Readable asReadable(Resource resource) {
if (resource instanceof Readable)
return (Readable) resource;
else if (resource instanceof Wrapper)
return (asReadable( ((Wrapper) resource).getSource() );
else
return null;
}