views:

76

answers:

3

Looking at the following (simplified) hierarchy of classes:

>     Email (base class) 
>     SimpleEmail extends Email
>     HtmlEmail extends Email

I need to decorate Email.send() to add throttling functionality. I need to instantiate SimpleEmail, HtmlEmail or other similar subclasses of Email.

What should this pattern look like exactly? My guess (which surly needs correcting) is as follows:

class abstract EmailDecorator
   -> Define a constructor: EmailDecorator(Email component)
   -> Implements all methods of Email and passes values through to component
   -> Adds functionality to send() method
class SimpleEmailDecorator extends EmailDecorator
   -> Define a constructor: SimpleEmailDecorator(SimpleEmail component)
   -> Implement all methods of SimpleEmail and pass through to component
class HtmlEmailDirector extends EmaiDecorator
   -> Same as SimpleEmailDecorator

My brain is not wrapping around how I properly deal with important existing subclasses of the base class that I need to "enhance". Most examples simplify it down to a point where the inheritance question becomes muddled.

A: 

Do you need special methods from SimpleEmail or HtmlEmail ?

If not then a non abstract EmailDecorator will be enough. If Email, SimpleEmail or HtmlEmail implement some interfaces, you should really implement them too.

Colin Hebert
I don't need to change any methods of HtmlEmail, or SimpleEmail. But I don't understand if I'm supposed to create concrete decorator classes for each of them and wrap all their methods as well. After all, ultimately I need to work with an instance of HtmlEmail or equivalent decorated class which contains the methods it extends from Email.It just seems like I'm creating all kinds of Objects here to re-create an entire hierarchy of objects under Email. That doesn't seem scalable or maintainable, especially if there are a lot of objects that extend Email, and objects that extend those objects.
David Parks
+4  A: 

Here's a simplified example of the decorator pattern. The class hierarchy is restructured as static inner classes so that the whole example is contained in one compilation unit (as seen on ideone.com):

public class AnimalDecorator {

    static abstract class Animal {
        public abstract String makeNoise();
    }   
    static class Dog extends Animal {
        @Override public String makeNoise() { return "woof"; }
    }
    static class Cat extends Animal {
        @Override public String makeNoise() { return "meow"; }
    }

    static class Normal extends Animal {
        protected final Animal delegate;
        Normal(Animal delegate)     { this.delegate = delegate; }
        @Override public String makeNoise() {
            return delegate.makeNoise();
        }
    }
    static class Loud extends Normal {
        Loud(Animal delegate)       { super(delegate); }
        @Override public String makeNoise() {
            return String.format("%S!!!", delegate.makeNoise());
        }       
    }
    static class Stuttering extends Normal {
        Stuttering(Animal delegate) { super(delegate); }
        @Override public String makeNoise() {
            return delegate.makeNoise().replaceFirst(".", "$0-$0-$0-$0");
        }
    }

    public static void keepPokingIt(Animal a) {
        // let's skip the details for now...
        System.out.println(a.makeNoise());
    }
    public static void main(String[] args) {
        keepPokingIt(new Cat());
        // meow

        keepPokingIt(new Stuttering(new Dog()));
        // w-w-w-woof

        keepPokingIt(new Loud(new Cat()));
        // MEOW!!!

        keepPokingIt(new Loud(new Stuttering(new Dog())));
        // W-W-W-WOOF!!!        
    }
}

So here we have a simple Animal hierarchy, with Dog and Cat subclasses. We also have a Normal decorator -- also an Animal -- that simply delegates all methods to another Animal. That is, it doesn't really do any effective decoration, but it's ready to be subclassed so that actual decorations can be added.

We only have one method here, makeNoise(). We then have two kinds of actual decorations, Loud and Stuttering. (Consider the case where Animal has many methods; then Normal would be most valuable).

We then have a keepPokingIt(Animal) method, which takes ANY Animal, and would do unmentionable things to it until it makeNoise(). In our main function, we then keepPokingIt various kinds of animals, decorated with various personality traits. Note that we can even stack one decoration on top of another.

The exact implementation details may vary, but this simplified example pretty much captures the essence of the decorator pattern.


Another example: ForwardingCollection hierarchy from Guava

In the above example, keepPokingIt only cares that it's an Animal. Sometimes you may want to just poke a Cat and not a Dog, or in other ways differentiate the two types. In those kinds of scenarios, you'd then provide NormalCat, NormalDog, etc.

If you design your type hierarchy well, this should not be a problem. Remember that you don't have to write decorators for each implementation class, but rather one for each type that you care about. Ideally, each type should even be an interface rather than a concrete class.

Consider the Java Collections Framework type hierarchy, for example. We have:

Guava conveniently facilitates decorator pattern implementations on top of this type hierarchy:

Note that there is no ForwardingHashMap<K,V>, or a ForwardingTreeSet<E>. There's probably no need for those anyway.

See also

  • Effective Java 2nd Edition, Item 18: Prefer interfaces to abstract classes

Related questions

polygenelubricants
A: 

If the sub-classes have additional methods, and you want those to be accessible through the decorators, then you will have to write a separate decorator for each subclass. For you particlular problem, I would recommend another solution. Remove the send-method from the Email-class and create a new class Mailer that is responsible for sending emails:

class Mailer {
    public void send(Email mail) {
        // get required info from mail
        String recipents = mail.getRecipents()
        String subject = mail.getSubject()
        String content = mail.getContent()
        // do stuff with all the information
    }
}

This way you can use different ways of sending emails with all types of emails.

Space_C0wb0y