Использование шаблона проектирования декоратора для иерархии классов - PullRequest
2 голосов
/ 24 августа 2010

Глядя на следующую (упрощенную) иерархию классов:

>     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

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

Ответы [ 3 ]

9 голосов
/ 24 августа 2010

Вот упрощенный пример шаблона декоратора . Иерархия классов реструктурируется как 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 . У нас есть:

Гуава удобно облегчает реализацию шаблонов декоратора поверх иерархии этого типа:

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

Смотри также

  • Effective Java 2nd Edition, Item 18: Предпочитают интерфейсы абстрактным классам

Похожие вопросы

1 голос
/ 24 августа 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
    }
}

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

0 голосов
/ 24 августа 2010

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

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

...