Использование шаблона проектирования декоратора для иерархии классов

Рассмотрим следующую (упрощенную) иерархию классов:

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

Мне нужно украсить Email.send (), чтобы добавить функцию регулирования. Мне нужно создать экземпляр SimpleEmail, HtmlEmail или других подобных подклассов электронной почты.

Как именно должен выглядеть этот узор? Мое предположение (которое требует исправления) выглядит следующим образом:

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

Мой мозг не думает о том, как правильно обращаться с важными существующими подклассами базового класса, которые мне нужно «улучшить». Большинство примеров упрощают его до такой степени, что вопрос о наследовании становится запутанным.


person David Parks    schedule 23.08.2010    source источник


Ответы (3)


Вот упрощенный пример шаблона декоратора. Иерархия классов реструктурирована как static внутренние классы, так что весь пример содержится в одной единице компиляции (как показано на 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 иерархия с подклассами Dog и Cat. У нас также есть Normal декоратор - также Animal, который просто делегирует все методы другому Animal. То есть, на самом деле он не делает какого-либо эффективного декора, но он готов к подклассу, чтобы можно было добавлять настоящие украшения.

У нас есть только один метод, makeNoise(). Затем у нас есть два вида настоящих украшений: Loud и Stuttering. (Рассмотрим случай, когда Animal имеет много методов; тогда Normal будет наиболее ценным).

Затем у нас есть keepPokingIt(Animal) метод, который принимает ЛЮБОЙ Animal и будет делать с ним неописуемые вещи, пока он не makeNoise(). В нашей main функции мы затем keepPokingIt различных животных, украшенных различными личностными чертами. Обратите внимание, что мы можем даже складывать одно украшение поверх другого.

Точные детали реализации могут отличаться, но этот упрощенный пример в значительной степени отражает суть шаблона декоратора.


Другой пример: ForwardingCollection иерархия из Гуавы

В приведенном выше примере keepPokingIt заботится только о том, что это Animal. Иногда вы можете захотеть просто ткнуть Cat, а не Dog, или другими способами различать эти два типа. В таких сценариях вы должны указать NormalCat, NormalDog и т. Д.

Если вы хорошо спроектируете иерархию типов, это не должно быть проблемой. Помните, что вам нужно писать декораторы не для каждой реализации class, а для каждого типа, который вам нужен. В идеале каждый тип должен быть даже interface, а не конкретным class.

Рассмотрим, например, иерархию типов Java Collections Framework. У нас есть:

Guava удобно упрощает реализацию шаблона декоратора поверх этой иерархии типов:

Обратите внимание, что нет ни ForwardingHashMap<K,V>, ни ForwardingTreeSet<E>. В любом случае, наверное, в этом нет необходимости.

Смотрите также

  • Эффективное второе издание Java, пункт 18: предпочитайте интерфейсы абстрактным классам

Связанные вопросы

person polygenelubricants    schedule 24.08.2010

Если у подклассов есть дополнительные методы, и вы хотите, чтобы они были доступны через декораторы, вам придется написать отдельный декоратор для каждого подкласса. Для вашей конкретной проблемы я бы порекомендовал другое решение. Удалите 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
    }
}

Таким образом, вы можете использовать разные способы отправки писем со всеми типами писем.

person Björn Pollex    schedule 24.08.2010

Вам нужны специальные методы из SimpleEmail или HtmlEmail?

В противном случае будет достаточно не абстрактного EmailDecorator. Если Email, SimpleEmail или HtmlEmail реализуют какие-то интерфейсы, вам действительно стоит их реализовать.

person Colin Hebert    schedule 23.08.2010
comment
Мне не нужно менять какие-либо методы HtmlEmail или SimpleEmail. Но я не понимаю, должен ли я создавать конкретные классы декораторов для каждого из них, а также обертывать все их методы. В конце концов, мне нужно работать с экземпляром HtmlEmail или эквивалентным декорированным классом, который содержит методы, которые он расширяет от Email. Просто кажется, что я создаю здесь все виды объектов, чтобы воссоздать целую иерархию объектов в электронной почте. Это не кажется масштабируемым или обслуживаемым, особенно если есть много объектов, расширяющих Email, и объектов, расширяющих эти объекты. - person David Parks; 24.08.2010