Слайд 1Паттерн Декоратор
Паттерн Декоратор: Добавляет дополнительные возможности к объекту динамически.
Декораторы предоставляют гибкую альтернативу наследованию для расширения функциональности.*
*Исходное определение, данное
в книге «Паттерны проектирования» GoF (Addison Wesley, 1994).
Слайд 3Суть паттерна
Декоратор
это структурный паттерн проектирования, который позволяет динамически добавлять
объектам новую функциональность, оборачивая их в полезные «обёртки».
Проблема
Вам нужно динамически
добавлять и снимать с объекта новые обязанности таким образом, чтобы он оставался совместим с остальным кодом программы.
Наследование — первое что приходит в голову, если вам нужно добавить новое поведение объекту. Но механизм наследования статичен — в программу нельзя на лету добавлять подклассы.
Решение
Декоратор имеет ещё одно название — Обёртка. Оно удачнее описывает суть паттерна. Итак, с Декоратором вы помещаете целевой объект в другой объект-обёртку, который расширяет базовое поведение объекта.
Оба объекта имеют общий интерфейс, поэтому пользователю всё равно с чем работать — с чистым объектом или обёрнутым.
Вы можете использовать несколько разных обёрток одновременно — результат будет иметь функции всех обёрток сразу.
Слайд 4Структура
Компонент задаёт общий интерфейс обёрток и оборачиваемых объектов.
Конкретный Компонент определяет
класс оборачиваемых объектов. Он содержит какое-то базовое поведение, которое потом
изменяют декораторы.
Базовый Декоратор хранит ссылку на объект-компонент, им может быть как Конкретный компонент, так и один из Конкретных декораторов. Декоратор делегирует все свои операции вложенному объекту. Дополнительное поведение содержится в Конкретных декораторах.
Конкретные Декораторы — это различные вариации декораторов, которые содержат добавочное поведение. Оно выполняется до или после вызова аналогичного поведения обёрнутого объекта.
Слайд 5Применимость
Паттерн можно часто встретить в Java-коде, особенно в коде, работающем
с потоками данных.
Примеры Декораторов в стандартных библиотеках Java:
Все подклассы java.io.InputStream, OutputStream, Reader и Writer имеют конструктор,
принимающий объекты этих же классов.
java.util.Collections, методы checkedXXX(), synchronizedXXX() и unmodifiableXXX().
javax.servlet.http.HttpServletRequestWrapper и HttpServletResponseWrapper
Признаки применения паттерна:
Декоратор можно распознать по создающим методам, которые принимают в параметрах объекты того же абстрактного типа или интерфейса, что и текущий класс.
Когда вам нужно добавлять обязанности объектам на лету, незаметно для кода, который их использует.
Объекты помещают в обёртки, имеющие дополнительные поведения.
Обёртки и сами объекты имеют одинаковый интерфейс, а значит для клиентов нет разницы с чем работать — обычным или обёрнутым объектом.
Когда нельзя расширить обязанности объекта с помощью наследования.
Во многих языках программирования есть ключевое слово final, которое может заблокировать наследование класса.
Расширить такие классы можно только с помощью Декоратора.
Слайд 6Пример: Шифрование и сжатие данных
Пример показывает, как можно добавить новую
функциональность объекту, не меняя его класса.
Сначала класс бизнес-логики мог только
считывать и записывать данные напрямую. Применив паттерн Декоратор, мы получаем небольшие классы-обёртки, которые добавляют новые поведения до или после основной работы вложенного объекта.
Первая обёртка шифрует и расшифрует данные, а вторая — сжимает и распаковывает их.
Мы можем использовать обёртки как отдельно друг от друга, так и все вместе, обернув один декоратор другим.
Слайд 7Интерфейс, задающий базовые операции чтения и записи данных
package decorator.example.decorators;
public
interface DataSource {
void writeData(String data);
String readData(); }
Слайд 8Класс, реализующий прямое чтение и запись данных
package decorator.example.decorators;
import java.io.*;
public class FileDataSource implements DataSource {
private String name;
public
FileDataSource(String name) {
this.name = name; }
@Override
public void writeData(String data) {
File file = new File(name);
try (OutputStream fos = new FileOutputStream(file)) {
fos.write(data.getBytes(), 0, data.length()); }
catch (IOException ex) { System.out.println(ex.getMessage()); } }
@Override
public String readData() {
char[] buffer = null;
File file = new File(name);
try (FileReader reader = new FileReader(file)) {
buffer = new char[(int) file.length()];
reader.read(buffer); }
catch (IOException ex) { System.out.println(ex.getMessage()); }
return new String(buffer); } }
Слайд 9Базовый декоратор
package decorator.example.decorators;
public class DataSourceDecorator implements DataSource {
private
DataSource wrappee;
public DataSourceDecorator(DataSource source) {
this.wrappee = source; }
@Override
public void writeData(String data) {
wrappee.writeData(data); }
@Override
public String readData() {
return wrappee.readData(); } }
Слайд 10Декоратор шифрования
package decorator.example.decorators;
import java.util.Base64;
public class EncryptionDecorator extends DataSourceDecorator
{
public EncryptionDecorator(DataSource source) { super(source); }
@Override
public void
writeData(String data) {
super.writeData(encode(data)); }
@Override
public String readData() {
return decode(super.readData()); }
public String readDecodedData() {
return decode(super.readData()); }
public String encode(String data) {
byte[] result = data.getBytes();
for (int i = 0; i < result.length; i++) {
result[i] += (byte) 1; }
return Base64.getEncoder().encodeToString(result); }
public String decode(String data) {
byte[] result = Base64.getDecoder().decode(data);
for (int i = 0; i < result.length; i++) {
result[i] -= (byte) 1; }
return new String(result); } }
Слайд 11Декоратор сжатия
package decorator.example.decorators;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import
java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
public
class CompressionDecorator extends DataSourceDecorator {
private int compLevel = 6;
public CompressionDecorator(DataSource source) { super(source); }
public int getCompressionLevel() { return compLevel; }
public void setCompressionLevel(int value) { compLevel = value; }
@Override
public void writeData(String data) { super.writeData(compress(data)); }
@Override
public String readData() { return decompress(super.readData()); }
Слайд 12сжатие
public String compress(String stringData) {
byte[] data = stringData.getBytes();
try { ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
DeflaterOutputStream dos =
new DeflaterOutputStream(bout, new Deflater(compLevel));
dos.write(data);
dos.close();
bout.close();
return Base64.getEncoder().encodeToString(bout.toByteArray()); }
catch (IOException ex) { return null; } }
public String decompress(String stringData) {
byte[] data = Base64.getDecoder().decode(stringData);
try { InputStream in = new ByteArrayInputStream(data);
InflaterInputStream iin = new InflaterInputStream(in);
ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
int b;
while ((b = iin.read()) != -1) { bout.write(b); }
in.close();
iin.close();
bout.close();
return new String(bout.toByteArray()); }
catch (IOException ex) { return null; } } }
Слайд 13Клиентский код
package decorator.example;
public class Demo {
public static void
main(String[] args) {
String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
DataSourceDecorator
encoded = new CompressionDecorator( new EncryptionDecorator( new FileDataSource("out/OutputDemo.txt"))); encoded.writeData(salaryRecords);
DataSource plain = new FileDataSource("out/OutputDemo.txt");
System.out.println("- Input ----------------");
System.out.println(salaryRecords);
System.out.println("- Encoded --------------");
System.out.println(plain.readData());
System.out.println("- Decoded --------------");
System.out.println(encoded.readData()); } }
Слайд 14Когда использовать паттерн Декоратор?
Есть три распространенные ситуации, в которых вы
могли бы рассмотреть возможность использования этого паттерна:
Нужно добавлять функциональность к
отдельным объектам динамически и прозрачно без влияния на другие объекты.
Нужно расширить поведение класса, с которым это делать было бы непрактично. Определение класса может быть скрыто и недоступно для наследования от него, или расширение поведения класса потребовало бы огромного количества подклассов для поддержания каждой комбинации возможностей.
Расширенные возможности класса могут быть опциональными.
Слайд 15Пример Декоратора из WIKI
public interface InterfaceComponent { void doOperation(); }
class MainComponent implements InterfaceComponent {
@Override
public void doOperation() {
System.out.print("World!"); } }
abstract class Decorator implements InterfaceComponent {
protected InterfaceComponent component;
public Decorator (InterfaceComponent c) { component = c; }
@Override
public void doOperation() {
component.doOperation(); }
public void newOperation() {
System.out.println("Do Nothing"); } }
Слайд 16Классы Декораторы
class DecoratorSpace extends Decorator {
public DecoratorSpace(InterfaceComponent c) {
super(c); }
@Override
public void doOperation() {
System.out.print(" ");
super.doOperation();
}
@Override
public void newOperation() {
System.out.println("New space operation"); } }
class DecoratorComma extends Decorator {
public DecoratorComma(InterfaceComponent c) { super(c); }
@Override
public void doOperation() { System.out.print(",");
super.doOperation(); }
@Override
public void newOperation() {
System.out.println("New comma operation"); } }
Слайд 17Класс Декораторы+Main
class DecoratorHello extends Decorator {
public DecoratorHello(InterfaceComponent c) {
super(c); }
@Override
public void doOperation() {
System.out.print("Hello");
super.doOperation(); }
@Override
public void newOperation() {
System.out.println("New hello operation"); } }
class Main {
public static void main (String... s) {
Decorator c = new DecoratorHello(new DecoratorComma(new DecoratorSpace(new MainComponent())));
c.doOperation(); // Результат выполнения?
c.newOperation(); // Результат выполнения ?
} }
Слайд 18Преимущества и недостатки
Большая гибкость, чем у наследования.
Позволяет добавлять обязанности на
лету.
Можно добавлять несколько новых обязанностей сразу.
Позволяет иметь несколько мелких объектов
вместо одного объекта на все случаи жизни.
Трудно конфигурировать многократно обёрнутые объекты.
Обилие крошечных классов
Слайд 19Отношения с другими паттернами
Адаптер предоставляет классу альтернативный интерфейс. Заместитель предоставляет
тот же интерфейс. Декоратор предоставляет расширенный интерфейс.
Адаптер меняет интерфейс существующего
объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.
Цепочку обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного выполнения операции через серию связанных объектов. Но есть и несколько важных отличий.
Обработчики в Цепочке обязанностей могут выполнять произвольные действия, независимые друг от друга, а также в любой момент прерывать дальнейшую передачу по цепочке. С другой стороны Декораторы расширяют какое-то определённое действие, не ломая интерфейс базовой операции и не прерывая выполнение остальных декораторов.
Слайд 20Отношения декоратора с другими паттернами
Компоновщик и Декоратор имеют похожие структуры
классов из-за того, что оба построены на рекурсивной вложенности. Она
позволяет связать в одну структуру бесконечное количество объектов.
Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей.
Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей дерева компонентов.
Архитектура, построенная на Компоновщиках и Декораторах часто может быть улучшена за счёт внедрения Прототипа. Он позволил бы клонировать сложные структуры, а не собирать заново.
Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».
Декоратор и Заместитель имеют похожие структуры, но разные назначения. Они похожи тем, что оба построены на композиции и делегировании работы другому объекту. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного объекта, а обёртывание Декораторов контролируется клиентом.
Слайд 21Шаги реализации
Убедитесь, что у в вашей задаче есть один основной
компонент и несколько опциональных дополнений или надстроек над ним.
Создайте интерфейс Компонента,
который содержал бы все общие методы для основного компонента и его дополнений.
Создайте класс Конкретного компонента и поместите в него основную бизнес-логику.
Создайте базовый класс декораторов. Он должен содержать ссылку на объект типа Компонент.
Оба класса должны реализовывать интерфейс Компонента.
Все методы Базового декоратора должны делегировать действие оборачиваемому объекту.
Создайте классы конкретных декораторов, наследуя их от Базового декоратора.
Конкретный декоратор должен выполнять свою добавочную функциональность, а затем (или перед этим) вызывать эту же операцию обёрнутого объекта.
Клиент берёт на себя ответственность за конфигурацию и порядок обёртывания объектов.
Слайд 22Пример использования декоратора
Приложение реализует формирование компьютерной конфигурации и вычисление
ее стоимости на основе составляющих объектов реализующих интерфейс Component
Слайд 23Класс клиента
public class Main {
public static void main(String[] args){
Component computer
= new Computer();
computer = new Keyboard(computer);
computer = new Mouse(computer);
computer =
new Memory(computer);
computer = new Memory(computer);
System.out.println("Order for my computer");
for (String part : computer.getDescription()){
System.out.println("Component: "+part);
}
System.out.println("Total cost: "+computer.getCost());
}
}
Слайд 24Интерфейсы Component и ComponentDecorator
public interface Component {
public ArrayList getDescription();
public double
getCost();
}
public interface ComponentDecorator extends Component {
public void setWrapped(Component
c);
}
Слайд 25Класс конкретного компонента
public class Computer implements Component {
@Override
public ArrayList getDescription()
{
ArrayList arrayList = new ArrayList();
arrayList.add("My computer");
return arrayList;
}
@Override
public double getCost() {
return
33000.0;
}
}
Слайд 26Класс декоратор -клавиатура
public class Keyboard implements ComponentDecorator {
Component wrapped;
public Keyboard(Component
c){ setWrapped(c); }
@Override
public void setWrapped(Component c) {
this.wrapped = c;
}
@Override
public ArrayList getDescription() {
ArrayList
arrayList = wrapped.getDescription();
arrayList.add("Logitech Keyboard K200");
return arrayList;
}
@Override
public double getCost() {
return wrapped.getCost() + 820.;
}
}
Слайд 27Класс декоратор -мышь
public class Mouse implements ComponentDecorator {
Component wrapped;
public Mouse(Component
c){
setWrapped(c);
}
@Override
public void setWrapped(Component c) {
this.wrapped = c;
}
@Override
public ArrayList getDescription() {
ArrayList
arrayList = wrapped.getDescription();
arrayList.add("SteelSeries Sensei MLG 62153 Black");
return arrayList;
}
@Override
public double getCost() {
return wrapped.getCost() + 7190.;
}
}
Слайд 28Класс декоратор -память
public class Memory implements ComponentDecorator {
Component wrapped;
public Memory(Component
c){
setWrapped(c);
}
@Override
public void setWrapped(Component c) {
this.wrapped = c;
}
@Override
public ArrayList getDescription() {
ArrayList
arrayList = wrapped.getDescription();
arrayList.add("Toshiba STOR.E 1TB");
return arrayList;
}
@Override
public double getCost() {
return wrapped.getCost() + 4630.;
}
}
Слайд 29Литература
https://ru.wikipedia.org/wiki/Декоратор_(шаблон_проектирования)
https://refactoring.guru/ru/design-patterns/decorator