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

3

我正在寻找一种替代装饰器模式使其更加动态的方法。举一个简单的例子,假设我们有以下代码:

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接口,但是它正在读取的资源实现了该接口。假设我们构造一个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));

生成的资源“丢失”了所有添加的功能(可读性、可列表等)。

让Rateable扩展Listable是荒谬的。

再次,我可以递归地检查resource.getSource()并找出所有功能。在即时回复中没有清晰的解决方案,因此也许递归检查是一个不错的选择?


你装饰这些对象的目的是什么? - flup
为什么Zip不能实现Readable - Aaron Digulla
1
你能提供一个具体的例子来说明这是一个问题吗? - Dai
@Dai 更新中提供的示例 - nablex
@flup 动态地为那些不知道这些特性存在的对象添加特性。 - nablex
@AaronDigulla 并非所有功能都相关,因此没有明确的层次结构可供使用(请参见更新的示例)。 - nablex
6个回答

2
我认为你正在寻找的是Mixin(混合)。
该链接的维基百科页面列出了支持Mixin的面向对象编程语言。你是否特别需要使用Java?

运行时混入(与编译时相对)可能提供一种解决方案,但我非常依赖于Java。 - nablex
1
Java有一个mixin实现,可以参考Qi4j。它通过从片段构建复合体来实现。但是对于您的目的来说,这可能是一个比较重量级的解决方案。 - flup
如果我正确理解了这个框架,Qi4j并不是“动态”的,因为组合和其他内容必须编译。然而,这些特性完全是动态的,不需要重新构建即可正常运行。 - nablex
@user1109519 是的!(假设我理解你的意思)请参阅intro。"对象的组成可能随时间而变化。" - flup

2

据我所知,您在这里追求的概念类似于鸭子类型,而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中解决此问题。


虽然Groovy很有趣,但不是一个选择,严格的类型检查是必须的。 - nablex
@user1109519 - 在这种情况下,您可以考虑在Java中使用一个鸭子类型库 - http://code.google.com/p/duckapter/。请注意它使用反射,但除非您在紧密循环中调用这些内容,否则不应对您产生太大影响。 - radai

0
也许适配器模式可以帮上忙:
Readable r = zip.adapt( Readable.class );

这个方法要求adapt()返回一个实现了Readable接口的zip实例。

实现通常使用一个“适配器管理器”,它知道如何为所有注册类型构建包装器。


虽然适配器模式在代码方面可能能够解决问题,但本身该模式(在我看来)并不是为此而设计的。它旨在将类似但具有不同接口的功能相互转换。而手头的问题则是全新的、不相关的功能。你可以将“Zip”适配成“Readable”,但我认为这距离该模式的意图太远了,不能称之为适配器模式。 - nablex
代码设计总是在矛盾目标之间做出选择。当你坚持要一个“纯净”的解决方案时,答案是:这是不可能的。Java 简单地没有这些工具。因此,你必须接受一些变通方法。适配器模式足够灵活,可以满足你的需求,同时清晰地传达意图。模式文档没有提到这种用例并不是反对使用的论据,因为文档可能是错误的。 - Aaron Digulla
完全正确,我的解决方案(请参见原问题或我的答案)遵循相同的策略(将其转换为所需类型),但没有实际适配器。因此,它与该模式偏离得太远。 - nablex
你要寻找的设计模式是桥接模式(Bridge Pattern)。http://en.wikibooks.org/wiki/Computer_Science/Design_Patterns/Bridge_pattern - Visionary Software Solutions

0

在装饰一个对象时,通常只装饰对象的一个接口,以修改或添加其行为的某个方面。您可以使用不同的装饰器装饰对象的另一个接口。这些装饰器可以同时存在。

您可以将一个装饰器传递给一个方法,将另一个装饰器传递给另一个方法。

当您希望首先使用多个装饰器装饰对象,然后通过代码传递该对象时,这会变得混乱。

因此,针对您的情况,我建议您将装饰器再次包装成一个单一对象,该对象知道资源的哪种装饰器存在。

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框架允许您以更清晰的方式(以及更多方式)使用注释来完成此操作。您可以将片段组合成复合体。不过,这需要一些时间来适应。为资源滚动自己的特定实现的优点是它会更容易向他人解释。


问题在于初始对象没有实现这些特性。这些特性是被添加进来的(它们本身也可以被装饰)。装饰器模式假定您只是覆盖现有的方法,而不是必须在运行时添加它们。 - nablex
“重新阅读”并不完全正确,你也可以使用装饰器添加行为。 - flup
是的,但如果您添加行为,则生成的装饰器仍旧需要实现已存在行为的所有方法,此时它变得不可行。问题确实在于我想传递一个装饰后的对象。 - nablex
您无需实现装饰器中的所有其他方法。生成的对象将是什么类型?您是否经常以相同的方式装饰对象? - flup
如果您想使用装饰对象访问这些方法,那么它就会生效。生成的对象主要是一种资源,在需要时会被探测特定的功能。有各种各样的功能以所有可能的组合形式存在。 - nablex

0

这里可能不太合适,但是一个动态的功能发现:

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

0

我将提供自己的建议(如原问题所述)作为答案。如果足够多的人认为它是一个值得的解决方案,或者没有更好的解决方案出现,我会接受它。

简而言之,我的解决方案是使用包装器接口向后遍历资源,以确定哪些功能存在。以以下示例为例:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接