Паттерн Декоратор – шаблон проектирования, предназначенный для динамического подключения к объекту дополнительного поведения. Паттерн Декоратор представляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Реализация: после применения последних двух паттернов давайте продолжим работать на автоконцерн и попробуем написать программу для расчета стоимости автомобиля на основании его комплектации. Ранее при рассмотрении шаблонов проектирования, как вы могли заметить, мы старались избегать механизма наследования отдавая предпочтение композиции. Сегодня мы нарушим нашу традицию и для этого есть серьезные причины.
Начнем работу с создания двух абстрактных классов один для машин, второй для дополнительных опций к машинам:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
abstract class Car{ String name = "Unnamed Car"; public String getInfo(){ return name; } public abstract int getPrice(); } abstract class Decorator extends Car{ public abstract String getInfo(); } |
Обратите внимание класс Decorator наследуется от класса Car! Это необходимо для согласования типов – декораторы должны относиться к тому же типу, что и декорируемые объекты.
декораторы должны относиться к тому же типу, что и декорируемые объекты
Метод getInfo() будет использоваться для перечисления дополнительных опций встраиваемых в машину, а метод getPrice() для подсчета окончательной цены автомобиля. Теперь, когда у нас есть основа нашего приложения приступим к созданию классов машин:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class AudiA3 extends Car{ public AudiA3(){ name = "Audi A3"; } public int getPrice(){ return 10_000; } } class AudiA4 extends Car{ public AudiA4(){ name = "Audi A4"; } public int getPrice(){ return 15_000; } } |
Оба класса машин наследуются от базового абстрактного класса Car и реализуют его метод getPrice() устанавливая индивидуальную цену на базовую модель машины. И последний штрих – классы для дополнительных опций.
Для простоты разработаем два класса – встроенный GPS навигатор и кондиционер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class GPS extends Decorator{ Car car; public GPS(Car car){ this.car = car; } public String getInfo() { return car.getInfo() + " + GPS"; } public int getPrice() { return car.getPrice() + 1500; } } class AirCondition extends Decorator{ Car car; public AirCondition(Car car){ this.car = car; } public String getInfo() { return car.getInfo() + " + Air Conditioning"; } public int getPrice() { return car.getPrice() + 1000; } } |
В конструкторе классов мы передаем объект автомобиля, а в методах getInfo() и getPrice() добавляем описание и цену для новой комплектации.
Пришло время протестировать наш паттерн:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Example{ public static void main(String[] args) { Car car1 = new AudiA3(); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); car1 = new GPS(car1); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); car1 = new AirCondition(car1); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); Car car2 = new AirCondition(new GPS(new AudiA4())); System.out.println(car2.getInfo()); System.out.println(car2.getPrice()); } } |
Вывод:
Audi A3
10000
Audi A3 + GPS
11500
Audi A3 + GPS + Air Conditioning
12500
Audi A4 + GPS + Air Conditioning
17500
Как видите все работает. В качестве закрепления материала можете в коде обработать невозможность установки сразу двух GPS приемников и добавить опцию к комплектации – «полный привод».
Классы должны быть открыты для расширения, но закрыты для изменений
Как Вы могли заметить Паттерн Декоратор достаточно прост в реализации и обладает рядом плюсов, в качестве основного плюса, я хотел бы отметить возможность динамически подключать новую функциональность объектам. Благодаря этому Java широко использует этот паттерн в классах ввода/вывода.
Паттерн Декоратор полный код примера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
package example; abstract class Car{ String name = "Unnamed Car"; public String getInfo(){ return name; } public abstract int getPrice(); } abstract class Decorator extends Car{ public abstract String getInfo(); } class AudiA3 extends Car{ public AudiA3(){ name = "Audi A3"; } public int getPrice(){ return 10_000; } } class AudiA4 extends Car{ public AudiA4(){ name = "Audi A4"; } public int getPrice(){ return 15_000; } } class GPS extends Decorator{ Car car; public GPS(Car car){ this.car = car; } public String getInfo() { return car.getInfo() + " + GPS"; } public int getPrice() { return car.getPrice() + 1500; } } class AirCondition extends Decorator{ Car car; public AirCondition(Car car){ this.car = car; } public String getInfo() { return car.getInfo() + " + Air Conditioning"; } public int getPrice() { return car.getPrice() + 1000; } } public class Example{ public static void main(String[] args) { Car car1 = new AudiA3(); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); car1 = new GPS(car1); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); car1 = new AirCondition(car1); System.out.println(car1.getInfo()); System.out.println(car1.getPrice()); Car car2 = new AirCondition(new GPS(new AudiA4())); System.out.println(car2.getInfo()); System.out.println(car2.getPrice()); } } |