将装饰器设计模式用于类的层次结构

时间:2010-08-23 22:08:32

标签: java design-patterns decorator

查看以下(简化)类的层次结构:

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

我需要修饰Email.send()以添加限制功能。 我需要实例化SimpleEmail,HtmlEmail或其他类似的Email子类。

这种模式究竟应该是什么样的? 我的猜测(需要纠正)如下:

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

我的大脑并没有围绕如何正确处理我需要“增强”的基类的重要现有子类。大多数示例都将其简化为继承问题变得混乱的一个点。

3 个答案:

答案 0 :(得分:9)

以下是decorator pattern的简化示例。类层次结构重组为static内部类,以便整个示例包含在一个编译单元(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!!!        
    }
}

所以这里我们有一个简单的Animal层次结构,包含DogCat个子类。我们还有Normal装饰器 - 也是Animal - 它只是将所有方法委托给另一个Animal。也就是说,它并没有真正做任何有效的装饰,但它已经准备好进行子类化,以便可以添加实际的装饰。

我们这里只有一个方法makeNoise()。然后,我们有两种实际装饰,LoudStuttering。 (考虑Animal有多种方法的情况;然后Normal最有价值。

然后我们有一个keepPokingIt(Animal)方法,该方法需要 ANY Animal,并且会在makeNoise()之前对其进行无法控制的事情。在我们的main函数中,我们然后keepPokingIt各种各样的动物,装饰着各种个性特征。请注意,我们甚至可以堆叠一个装饰在另一个装饰之上。

确切的实现细节可能会有所不同,但这个简化的示例几乎捕获了装饰器模式的本质。


另一个例子:来自Guava的ForwardingCollection层次结构

在上面的例子中,keepPokingIt只关心它是Animal。有时您可能只想戳Cat而不是Dog,或以其他方式区分这两种类型。在这种情况下,您可以提供NormalCatNormalDog

如果你很好地设计你的类型层次结构,这应该不是问题。请记住,您不必为每个实现class编写装​​饰器,而是为您关心的每个类型编写一个装饰器。理想情况下,每种类型甚至应该是interface而不是具体的class

例如,考虑Java Collections Framework类型层次结构。我们有:

Guava可以方便地在此类型层次结构上实现装饰器模式实现:

请注意,没有ForwardingHashMap<K,V>ForwardingTreeSet<E>。反正可能没有必要。

另见

  • Effective Java 2nd Edition,Item 18:首选接口到抽象类

相关问题

答案 1 :(得分:1)

如果子类有其他方法,并且您希望通过装饰器访问这些方法,则必须为每个子类编写单独的装饰器。对于您的特殊问题,我建议另一种解决方案。从send - 类中删除Email - 方法,并创建一个负责发送电子邮件的新类Mailer

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

通过这种方式,您可以使用不同的方式发送包含所有类型电子邮件的电子邮件。

答案 2 :(得分:0)

您需要SimpleEmail或HtmlEmail的特殊方法吗?

如果没有,那么非抽象的EmailDecorator就足够了。如果Email,SimpleEmail或HtmlEmail实现了某些接口,你也应该真正实现它们。