JavaRush /Java блог /Random UA /Паттерн Декоратор Java (Decorator)

Паттерн Декоратор Java (Decorator)

Стаття з групи Random UA
У книзі Head First. Патерни проектування” Еріка Фрімена та Елізабет Робсон дається таке визначення: патерн Декоратор динамічно наділяє об'єкт новими можливостями та є альтернативою субкласуванню у сфері розширення функціональності. Спробуймо на прикладі розглянути це визначення докладніше. Припустимо, Ви створабо одну чергову сучасну релігію і плануєте надавати людям відповідні послуги. Т.к. сучасні тенденції переслідують вегетаріанство, екологію, розвиток людини, а «традиційні» релігії (або атеїзм зрештою) людей чомусь не влаштовують, то ви слідуючи мейнстриму створюєте чергову релігію New Age (якийсь синтез із наявних релігій, взявши з кожної те що подобається). На початку ви надаєте такі послуги: 1. Ворожіння 2. Гороскоп Т.е. виглядає все так: Є інтерфейс послуги з ціною природно :) і описом
public interface Service {
    public double getPrice();
    public String getLabel();
}
та послуги
public class Divination implements Service {
    private String label;
    private double price;

    public Divination(String label, double price) {
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }

    public String getLabel() {
        return this.label;
    }
}
public class Horoscope implements Service {
    private String label;
    private double price;

    public Horoscope(String label, double price) {
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }

    public String getLabel() {
        return this.label;
    }
}
Ну і відповідно з'явилося 2 замовлення (ворожіння на картах Таро та персональний гороскоп від клієнта):
public static void main(String[] args) {
    double cost;
    // Гадание на Таро
    Service taro = new Divination("Таро", 1000);
    // И персональный гороскоп
    Service personalHoroscope = new Horoscope("Персональный гороскоп", 9000);
    cost = taro.getPrice() + personalHoroscope.getPrice();

    System.out.println(cost);
}
та результат роботи програми:
10000.0
Все було б добре, але таких як Ви вже сотні, якщо не тисячі і потрібно продовжувати розвивати духовність людей, бо їм уже не цікаво. Тому, як варіант, були запропоновані додаткові опції до поточних послуг. Так наприклад при виборі послуги ворожіння (Таро або на кавовій гущі) додатковою опцією можна замовити характеристику чакр або аури (зі своєю вартістю кожної). Як це можна було б реалізувати, щоб не вносити зміни до наявних класів послуги, де вже все налаштовано та правильно вважається. Можна створити додаткові класи Ворожіння + Чакри або Ворожіння + Аура до поточного Ворожіння:
public class Divination implements Service {
    // Здесь своя стоимость и другие методы
}
public class DivinationWithChakras implements Service {
    // Здесь своя стоимость и другие методы
}
public class DivinationWithAura implements Service {
    // Здесь своя стоимость и другие методы
}
чи використовувати субклассирование, тобто. розширювати батьківський клас дочірнім
public class DivinationWithAura extends Divination {
    public DivinationWithAura(String label, double price) {
        super(label, price);
    }
    // Здесь своя стоимость и другие методы
}
public class DivinationWithChakras extends Divination {
    public DivinationWithChakras(String label, double price) {
        super(label, price);
    }
    // Здесь своя стоимость и другие методы
}
Але мінуси видно відразу, розвиваючи духовність всього світу у нас можуть з'явитися нові додаткові опції, а значить нові класи, а якщо ще потрібно комбінувати поточні, то класи будуть стрімко розростатися, як мінімум вже не вистачає класу Ворожіння з двома опціями разом, а не по окремо:
public class DivinationWithChakrasAndAura implements Service {
    // Здесь своя стоимость и другие методы
}
Ось тут можна використовувати цей «рятівний» патерн Декоратор в Java. Для цього ми створимо клас для додаткових опцій, який також імплементуватиме Service, але також міститиме Service. І відповідно коли нам потрібно зробити замовлення з ворожіння та ще з 2 опціями разом, то виглядатиме це наступним чином: Інтерфейс як і було з самого початку
public interface Service {
    public double getPrice();
    public String getLabel();
}
2 класи послуг, як і було раніше:
public class Divination implements Service {
    private String label;
    private double price;

    public Divination(String label, double price) {
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }

    public String getLabel() {
        return this.label;
    }
}
public class Horoscope implements Service {
    private String label;
    private double price;

    public Horoscope(String label, double price) {
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }

    public String getLabel() {
        return this.label;
    }
}
Декоратор для додаткових опцій
public class OptionDecorator implements Service {
    private Service service;
    private String label;
    private double price;

    public OptionDecorator(Service service, String label, double price) {
        this.service = service;
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price + service.getPrice();
    }

    public String getLabel() {
        return this.label + service.getLabel();
    }
}
І самі налаштування (поки 2 штуки):
public class Aura extends OptionDecorator {
    public Aura(Service service) {
        super(service, "Характеристика ауры", 1500);
    }
}
public class Chakra extends OptionDecorator {
    public Chakra(Service service) {
        super(service, "Характеристика чакр", 500);
    }
}
Ну і саме замовлення
public static void main(String[] args) {
    // Гадание на Таро
    Service taro = new Divination("Таро", 1000);
    Service chakra = new Chakra(taro);
    Service aura = new Aura(chakra);

    // И общая стоимость
    System.out.println(aura.getPrice());
}
3000.0
який дає результат суми основної послуги та 2 додаткових опцій. А значить, не потрібно на кожну додаткову. опцію (або їхню комбінацію) створювати новий клас. До того ж, дані опції можна застосовувати не тільки до послуги Ворожіння, але також і до послуги Гороскоп. Тому коли в найближчому майбутньому нам потрібно буде впровадити такі додаткові опції: - сумісність партнерів по аватарках в соціальних мережах - поліпшення грошового потоку через дистанційний ченненлінг нам потрібно буде написати всього 2 додаткові класи:
public class Channeling extends OptionDecorator {
    public Channeling(Service service) {
        super(service, "Полет в Поле Чудес", 99999);
    }
}
public class Avatar extends OptionDecorator {
    public Avatar(Service service) {
        super(service, "Ваша любовь в соц сетях", 5555);
    }
}
і можна додавати їх до будь-якої послуги:
public static void main(String[] args) {
    // Гадание на Таро
    Service taro = new Divination("Таро", 1000);
    Service chakra = new Chakra(taro);
    Service aura = new Aura(chakra);

    // И общая стоимость
    System.out.println(aura.getPrice());

    // Гороскоп
    Service horoscope = new Horoscope("Персональный гороскоп", 1000);
    Service channenling = new Channeling(horoscope);
    Service avatar = new Avatar(channenling);

    // И общая стоимость
    System.out.println(avatar.getPrice());
}
та результат роботи програми (який нам потрібен):
3000.0
106554.0
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ