面向对象编程:动态添加特性



我正在寻找装饰器模式的替代方案,使其更具动态性。作为一个简单的例子,假设我们有这样的代码:

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;
}

最新更新