Статьи

Java 8 лямбда-выражение для шаблонов проектирования — шаблон оформления декоратора

Шаблон Decorator (также известный как Wrapper ) позволяет добавлять поведение к отдельному объекту, статически или динамически, без влияния на поведение других объектов того же класса. Это можно рассматривать как альтернативу подклассам. Мы знаем, что подклассы добавляют поведение во время компиляции, и изменение влияет на все экземпляры исходного класса. С другой стороны, декорирование может обеспечить новое поведение во время выполнения для выборочных объектов.

Декоратор соответствует интерфейсу компонента, который он украшает, так что он прозрачен для клиентов компонента. Декоратор передает запросы компоненту и может выполнять дополнительные действия до или после пересылки. Прозрачность позволяет рекурсивно вкладывать декораторы, что позволяет неограниченное количество дополнительных обязанностей. Ключевые участники шаблона Decorator представлены ниже:

декоратор

  • Компонент — Определяет интерфейс для объектов, к которым можно динамически добавлять обязанности.
  • ConcreteComponent — Определяет объект, к которому могут быть добавлены дополнительные обязанности
  • Декоратор — сохраняет ссылку на объект Компонента и соответствует интерфейсу Компонента. Он содержит объект Компонент, который будет оформлен.
  • ConcreteDecorator — добавляет ответственность к компоненту.

Теперь давайте рассмотрим конкретный пример шаблона декоратора и посмотрим, как он преобразуется с помощью лямбда-выражений. Предположим, у нас есть разные типы книг, и эти книги могут быть в разных обложках или разных категориях. Мы можем выбрать любую книгу и указать категорию или язык, используя наследование. Книгу можно абстрагировать как класс. После этого любой другой класс может расширить класс Book и переопределить методы для обложки или категории. Но это не эффективный подход. При таком подходе подклассы могут иметь ненужные методы, расширенные из суперкласса. Также, если бы нам пришлось добавить больше атрибутов или характеристик, в родительском классе произошли бы изменения. Изменения в реализации класса должны быть последним средством.

Давайте выберем оптимальный подход с использованием шаблона Decorator. Мы сделаем интерфейс для Book с основным методом:

1
2
3
4
5
public interface Book {
 
    public String describe();
 
}

Класс BasicBook может реализовать этот интерфейс для обеспечения минимальной абстракции:

01
02
03
04
05
06
07
08
09
10
public class BasicBook implements Book {
 
    @Override
    public String describe() {
 
        return "Book";
 
    }
 
}

Далее, давайте определим абстрактный класс BookDecorator, который будет действовать как Декоратор:

01
02
03
04
05
06
07
08
09
10
11
12
13
abstract class BookDecorator implements Book {
 
    protected Book book;
 
    BookDecorator(Book book) {
        this.book = book;
    }
 
    @Override
    public String describe() {
        return book.describe();
    }
}

Класс BookDecorator соответствует интерфейсу Book, а также хранит ссылку на интерфейс Book. Если мы хотим добавить категорию в качестве свойства к интерфейсу Book, мы можем использовать конкретный класс, который реализует интерфейс BookDecorator. Для категории Fiction у нас может быть следующий декоратор:

01
02
03
04
05
06
07
08
09
10
11
public class FictionBookDecorator extends BookDecorator {
 
    FictionBookDecorator(Book book) {
        super(book);
    }
 
    @Override
    public String describe() {
        return ("Fiction " + super.describe());
    }
}

Вы можете видеть, что FictionBookDecorator добавляет категорию книги в оригинальной операции, т.е. Аналогично, если мы хотим указать категорию «Наука», у нас может быть соответствующий ScienceBookDecorator :

01
02
03
04
05
06
07
08
09
10
11
public class ScienceBookDecorator extends BookDecorator {
 
    ScienceBookDecorator(Book book) {
        super(book);
    }
 
    @Override
    public String describe() {
        return ("Science " + super.describe());
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
public class SoftCoverDecorator extends BookDecorator {
 
    SoftCoverDecorator(Book book) {
        super(book);
    }
     
    @Override
    public String describe() { 
        return (super.describe() + " with Soft Cover");
    }
}

У нас также может быть HardCoverDecorator, описывающий, что книга имеет твердый переплет.

01
02
03
04
05
06
07
08
09
10
11
public class HardCoverDecorator extends BookDecorator {
     
    HardCoverDecorator(Book book) {
        super(book);
    }
     
    @Override
    public String describe() { 
        return (super.describe() + " with Hard Cover");
    }
}

Теперь давайте объединим все классы и интерфейсы, определенные для использования возможностей шаблона Decorator. Посмотрите только один пример взаимодействия всех этих классов:

01
02
03
04
05
06
07
08
09
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
import java.util.List;
import java.util.ArrayList;
 
public class BookDescriptionMain {
     
    public static void main(String [] args) {
         
        BasicBook book = new BasicBook();
         
        //Specify book as Fiction category
        FictionBookDecorator fictionBook = new FictionBookDecorator(book);
         
        //Specify that the book has a hard cover
        HardCoverDecorator hardCoverBook = new HardCoverDecorator(book);
         
        //What if we want to specify both the category and cover type together
        HardCoverDecorator hardCoverFictionBook = new HardCoverDecorator(fictionBook);             
         
        //Specify book as Science category
        ScienceBookDecorator scienceBook = new ScienceBookDecorator(book);
         
        //What if we want to specify both the category and cover type together
        HardCoverDecorator hardCoverScienceBook = new HardCoverDecorator(scienceBook);             
 
        //Add all the decorated book items in a list
        List<Book> bookList = new ArrayList<Book>() {
            {
                add(book);
                add(fictionBook);
                add(hardCoverBook);
                add(hardCoverFictionBook);
                add(scienceBook);
                add(hardCoverScienceBook);
            }
        };
         
        //Traverse the list to access all the book items
        for(Book b: bookList) {
            System.out.println(b.describe());
        }      
    }
}

Запуск этого дает следующий вывод:

1
Book
1
Fiction Book
1
Book with Hard Cover
1
Fiction Book with Hard Cover
1
Science Book
1
Science Book with Hard Cover

Это ясно демонстрирует, как различные свойства могут быть добавлены к любому заранее определенному классу / объекту. Кроме того, несколько свойств могут быть объединены. Я попытался объединить все украшенные элементы книги в списке, а затем получить к ним доступ, перебирая список.

То, что мы видели до сих пор, — это просто стандартный шаблон декоратора, и он существует уже давно. В наше время, когда функциональное программирование является новым модным словом, можно задуматься над тем, можно ли по-другому сделать поддержку лямбда-выражений в Java. Действительно, поскольку оформленный интерфейс подобен интерфейсу функции, мы можем перефразировать, используя лямбда-выражения в Java. Давайте посмотрим, как выглядит код:

01
02
03
04
05
06
07
08
09
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
import java.util.List;
import java.util.ArrayList;
 
public class BookDescriptionMainWithLambda {
     
    public static void main(String [] args) {
         
        BasicBook book = new BasicBook();
         
        //Specify book as Fiction category using Lambda expression
        Book fictionBook = () -> "Fiction " + book.describe();
         
        //Specify that the book has a hard cover using Lambda expression
        Book hardCoverBook = () -> book.describe() + " with Hard Cover";
         
        //What if we want to specify both the category and cover type together
        Book hardCoverFictionBook = () -> fictionBook.describe() + " with Hard Cover";              
         
        //Specify book as Science category using Lambda expression
        Book scienceBook = () -> "Science " + book.describe();
         
        //What if we want to specify both the category and cover type together
        Book hardCoverScienceBook = () -> fictionBook.describe() + " with Hard Cover";              
 
        List<Book> bookList = new ArrayList<Book>() {
            {
                add(book);
                add(fictionBook);
                add(hardCoverBook);
                add(hardCoverFictionBook);
                add(scienceBook);
                add(hardCoverScienceBook);
            }
        };
         
        bookList.forEach(b -> System.out.println(b.describe()));
    }
}

Запуск этого дает похожий вывод:

1
Book
1
Fiction Book
1
Book with Hard Cover
1
Fiction Book with Hard Cover
1
Science Book
1
Fiction Book with Hard Cover

Мы видим, что использование лямбда-выражений делает дополнительные классы для декораторов избыточными. Вам не нужны дополнительные занятия; просто укажите дополнительное поведение с помощью лямбда-выражения. Тем не менее, есть поддержка поиска декоратора для повторного использования. Если у вас есть конкретный класс декоратора, вы можете использовать его в следующий раз.