Tuesday, 13 November 2012

Problems with the Decorator pattern

We're often told to favor composition over inheritance, but I just got caught by a subtle bug using delegation and was wondering whether anyone else had fallen into the same trap and/or whether there's an easy way to detect or avoid the issue. Maybe I'm misusing the decorator pattern and, if so, I'd like to know how to use it properly.

Here's an example. Sorry it's a bit long.

I have an interface:

public interface Request { 

boolean hasRemoteUser();

String getRemoteUser();
}

And a simple implementation:

public class SimpleRequest implements Request { 

@Override
public boolean hasRemoteUser() {
return getRemoteUser() != null;
}

@Override
public String getRemoteUser() {
return null;
}
}

Approach 1: Inheritance

The SimpleRequest code is self-encapsulated, i.e. hasRemoteUser() is defined in terms of getRemoteUser() which means if I extend the class like this:

public class ExtendedRequest extends SimpleRequest { 

@Override
public String getRemoteUser() {
return "fred";
}
}

The following tests will pass:

Request extendedRequest = new ExtendedRequest(); 
assertEquals("fred", extendedRequest.getRemoteUser());
assertTrue(extendedRequest.hasRemoteUser());

Approach 2: Composition

I don't want to use inheritance, though. My requests are passed through a filter which adds the user information, so the decorator pattern is more suitable.

I start by writing a ForwardingRequest class that forwards all method calls to a delegate request.

public class ForwardingRequest implements Request { 

private final Request delegate;

public ForwardingRequest(Request request) {
this.delegate = request;
}

@Override
public boolean hasRemoteUser() {
return delegate.hasRemoteUser();
}

@Override
public String getRemoteUser() {
return delegate.getRemoteUser();
}
}

Now I can write code like this:

Request simpleRequest = new SimpleRequest(); 

Request alteredRequest = new ForwardingRequest(simpleRequest) {

@Override
public String getRemoteUser() {
return "fred";
}
};

The following test passes:

assertEquals("fred", alteredRequest.getRemoteUser()); 

But unfortunately this one does not:

assertTrue(alteredRequest.hasRemoteUser()); 

Why not? Because the hasRemoteUser() method of the delegate does not call the getRemoteUser() method of the ForwardingRequest, but calls the getRemoteUser() method of SimpleRequest (which returns null).

The Danger of Decorating

It seems to me that this kind of defect could occur in any code where forwarding is used and methods are overridden. Even if the code works now, there's nothing to say that a subtle bug couldn't be introduced later on by changes to the wrapped class.

It's left me a bit concerned. Have I missed something important? Am I using the pattern wrongly? Or is this just another reason to write lots of tests?

7 comments:

Markus Gärtner said...

The way you wrote the code, you would need a two way delegation model, thereby introducing a loop in your design. This is bad.

I think this has to do with your production class being degenerative. In fact, you should add a field to the SimpleRequest that will hold the remote user, and gets initialized in the constructor. Then you can add a decorator in the way that you did without breaking the behavior of the decorator or the implementation. The decorator should not implement the the getRemoteUser method here since it introduces the dependency from the SimpleRequest to the decorator which is bad. The decorator should carry all the logic, not the decorated class as it would need to be in this case.

Daniel Yokomizo said...

The inheritance example works because you implicitly relies on open recursion on 'this' in 'hasRemoteUser'. The only way most OO languages allow you to override 'this' is via inheritance.

You could make the recursion explicit using a 'self' parameter (e.g. "boolean hasRemoteUser(Request self) { return self.getRemoteUser(self) != null}") but this is usually too awkward to use.

David Peterson said...

@Markus Thanks. In my real case I couldn't change the implementation for SimpleRequest. I had to decorate it.

The real Request interface had about 40 methods on it. I guess my question is how do you go about determining what "all the logic" is that the decorator should carry?

It's clear that "hasRemoteUser()" and "getRemoteUser()" are related, but what happens if another method you weren't necessarily expecting to use one of these methods is actually using it? I guess the danger is small, but I was wondering if there was a way to prevent the issue completely.

zeroflag said...

I think this is not a problem of decorators, but the problem with inheritance. It shows that if you are using on inheritance, you are depending on inner implementation details rather than the outer interface.

David Peterson said...

@zeroflag
Hmm, yes, I see what you mean. The inherited version only works if the super-class has been explicitly designed to work when the getRemoteUser() method is overridden on its own. It depends on that particular implementation in the super-class, which isn't part of the explicit interface.

Cástor said...

Great blog ;)

Is it possible for you to share your twitter account?

I would like to follow you and stay up-to-date with your post through twitter.

Thank you.

David Peterson said...

Thanks. My Twitter account is @davidp99, but I don't write on it any more! (I find it absorbs too much time.)