面向对象编程:动态添加功能

时间:2013-03-27 08:39:46

标签: java oop

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

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()并找出所有功能。在即时回复中没有明确的解决方案,所以也许递归检查毕竟是一个很好的选择?

6 个答案:

答案 0 :(得分:2)

这听起来像你在这里努力的概念是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中解决这个问题。

答案 1 :(得分:2)

我认为你要找的是mixins

链接的维基百科页面有一个很好的支持它们的OOP语言列表。或者你是否特别依赖Java?

答案 2 :(得分: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 framework允许您使用注释以更干净的方式执行此操作(以及更多)。您将碎片组合成复合材料。不过,确实需要一些时间来适应。为资源推出自己的特定实现的优势在于,向其他人解释会更容易。

答案 3 :(得分:0)

也许adapter pattern可以提供帮助:

Readable r = zip.adapt( Readable.class );

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

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

答案 4 :(得分: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);

答案 5 :(得分:0)

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

简而言之,我自己的解决方案包括使用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;
}