nablex
nablex

Reputation: 4757

Object oriented programming: adding features on the fly

I'm looking for an alternative to the decorator pattern to make it more dynamic. As a simplistic example, let's say we have this code:

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) { ... }
}

As you can see, Zip does not directly implement Readable, however the resource it is reading from does. Suppose we construct a zip:

Zip zip = new Zip(new File());

I don't want (and can't) stack all the interfaces to extend each other (e.g. Listable extends Readable), nor can I construct all the objects to implement all the features because not all features are related to one another, you want to be able to "decorate" objects on the fly by wrapping them.

I'm sure this is a common problem but is there a pattern to solve it? Using the "Wrapper" interface you can of course probe the chain of resources to check for features if you want but I'm not sure if this is a sane approach.

UPDATE

The problem is as stated above that not all features are related so you can't build a nice hierarchy of interfaces. For example suppose you have this arbitrary new feature:

interface Rateable extends Resource {
    public int getRating();
}

class DatabaseRateable implements Rateable, Wrapper {
    public DatabaseRateable(Resource resource) { ... }
}

If you run:

Resource resource = new DatabaseRateable(new Zip(new File));

The resulting resource has "lost" all the features (readable, listable,...) that were added. It would be ridiculous to have Rateable extend say Listable.

Once again I could recursively check resource.getSource() and find out all the features. In the immediate replies there was no clear solution so perhaps the recursive check is a good option after all?

Upvotes: 3

Views: 420

Answers (6)

radai
radai

Reputation: 24192

it sounds to me like the concept your striving for here is duck typing, which java itself does not do natively (see my comment about a reflection-based java library for this). however, other languages running on the JVM certainly do. for example - 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()
}

you could write your code in groovy and have it seamlessly work alongside the rest of your java code, and solve this issue in groovy.

Upvotes: 2

nablex
nablex

Reputation: 4757

I will offer my own suggestion (as presented in the original question) as an answer. If enough people think it a worthy solution or a better solution is not forthcoming, I will accept it.

In short, my own solution consists of using the Wrapper interface to walk backwards through the resources to figure out which features are present. Given the example:

Resource resource = new DatabaseRateable(new Zip(new File));

You could imagine doing this:

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

Upvotes: 0

flup
flup

Reputation: 27104

When decorating an object, you usually decorate one interface of the object only, to modify or add only one aspect of its behavior. You can decorate another interface of the object with a different decorator. These decorators can exist at the same time.

You can pass one decorator to one method, the other to another.

This gets hacky when you wish to decorate the object first with several decorators and then pass the object around through your code.

So for your case, I'd suggest you wrapper the decorators into one single object again, which knows what kind of decorators exist for resources.

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

You can then pass around the resource and its users can discover which features this particular resource has.

The Qi4j framework allows you to do this (and more) in a cleaner way, using annotations. You compose Fragments into Composites. It does take some getting used to, though. The advantage of rolling your own specific implementation for Resources is that it'll be easier to explain to others.

Upvotes: 0

Joop Eggen
Joop Eggen

Reputation: 109532

Here maybe not so fitting, but a dynamic feature discovery:

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

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328536

Maybe the adapter pattern can help:

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

This asks the method adapt() to return an instance of zip which implements the Readable interface.

The implementations usually uses an "adapter manager" which knows how to build wrappers for all registered types.

Upvotes: 0

Derek Gourlay
Derek Gourlay

Reputation: 279

I think what you are looking for are mixins

The linked wikipedia page has a good list of OOP languages that support them. Or are you specifically tied to Java?

Upvotes: 2

Related Questions