Статьи

Шаблон оформления декоратора с использованием лямбд

С появлением лямбда-выражений в Java у нас появился новый инструмент для лучшего проектирования нашего кода. Конечно, первым шагом является использование потоков, ссылок на методы и других полезных функций, представленных в Java 8.

В будущем я думаю, что следующим шагом будет пересмотр хорошо зарекомендовавших себя шаблонов проектирования и просмотр их через линзы функционального программирования. Для этой цели я возьму Pattern Decorator и реализую его, используя лямбды.

Мы возьмем простой и вкусный пример шаблона Decorator: добавление начинки к пицце. Вот стандартная реализация, предложенная GoF:

Сначала у нас есть интерфейс, который определяет наш компонент:

1
2
3
public interface Pizza {
    String bakePizza();
}

У нас есть конкретный компонент:

1
2
3
4
5
6
public class BasicPizza implements Pizza {
    @Override
    public String bakePizza() {
        return "Basic Pizza";
    }
}

Мы решили, что мы должны украсить наш компонент по-разному. Мы идем с декоратором Pattern. Это абстрактный декоратор:

01
02
03
04
05
06
07
08
09
10
11
12
public abstract class PizzaDecorator implements Pizza {
    private final Pizza pizza;
     
    protected PizzaDecorator(Pizza pizza) {
        this.pizza = pizza;
    }
 
    @Override
    public String bakePizza() {
        return pizza.bakePizza();
    }
}

мы предоставляем некоторые конкретные декораторы для компонента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ChickenTikkaPizza extends PizzaDecorator {
    protected ChickenTikkaPizza(Pizza pizza) {
        super(pizza);
    }
 
    @Override
    public String bakePizza() {
        return super.bakePizza() + " with chicken topping";
    }
}
 
public class ProsciuttoPizza extends PizzaDecorator {
 
    protected ProsciuttoPizza(Pizza pizza) {
        super(pizza);
    }
 
    @Override
    public String bakePizza() {
        return super.bakePizza() + " with prosciutto";
    }
}

и это способ использовать новую структуру:

1
2
3
4
5
Pizza pizza = new ChickenTikkaPizza(new BasicPizza());
String finishedPizza = pizza.bakePizza();   //Basic Pizza with chicken topping
 
pizza = new ChickenTikkaPizza(new ProsciuttoPizza(new BasicPizza()));
finishedPizza  = pizza.bakePizza();  //Basic Pizza with prosciutto with chicken topping

мы можем видеть, что это может стать очень грязным, и это действительно стало очень грязным, если мы думаем о том, как мы обращаемся с буферизованными читателями в Java:

1
new DataInputStream(new BufferedInputStream(new FileInputStream(new File("myfile.txt"))))

Конечно, вы можете разделить это на несколько строк, но это не решит беспорядок, а просто распространит его. Теперь давайте посмотрим, как мы можем сделать то же самое, используя лямбды. Мы начнем с тех же базовых компонентов:

01
02
03
04
05
06
07
08
09
10
public interface Pizza {
    String bakePizza();
}
 
public class BasicPizza implements Pizza {
    @Override
    public String bakePizza() {
        return "Basic Pizza";
    }
}

Но теперь вместо того, чтобы объявлять абстрактный класс, который будет предоставлять шаблон для украшений, мы создадим декоратор, который запрашивает у пользователя функции, которые будут украшать компонент.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class PizzaDecorator {
    private final Function<Pizza, Pizza> toppings;
 
    private PizzaDecorator(Function<Pizza, Pizza>... desiredToppings) {
        this.toppings = Stream.of(desiredToppings)
                .reduce(Function.identity(), Function::andThen);
 
    }
 
     
    public static String bakePizza(Pizza pizza, Function<Pizza, Pizza>... desiredToppings) {
        return new PizzaDecorator(desiredToppings).bakePizza(pizza);
    }
 
private String bakePizza(Pizza pizza) {
    return this.toppings.apply(pizza).bakePizza();
}
 
}

Вот эта строка, которая строит цепочку украшений, которые будут применены:

1
Stream.of(desiredToppings).reduce(identity(), Function::andThen);

Эта строка кода возьмет ваши украшения (которые имеют тип Function) и объединит их в цепочку, используя andThen. Это так же как

1
(currentToppings, nextTopping) -> currentToppings.andThen(nextTopping)

и он уверен, что функции вызываются впоследствии в указанном вами порядке. Также Function.identity () переводится в лямбда-выражение elem -> elem. Хорошо, теперь, где мы будем определять наши украшения? Вы можете добавить их как статические методы в PizzaDecorator или даже в интерфейсе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public interface Pizza {
    String bakePizza();
 
    static Pizza withChickenTikka(Pizza pizza) {
        return new Pizza() {
            @Override
            public String bakePizza() {
                return pizza.bakePizza() + " with chicken";
            }
        };
    }
 
    static Pizza withProsciutto(Pizza pizza) {
        return new Pizza() {
            @Override
            public String bakePizza() {
                return pizza.bakePizza() + " with prosciutto";
            }
        };
    }
}

А теперь вот как этот шаблон будет использоваться:

1
2
3
4
5
String finishedPizza = PizzaDecorator.bakePizza(new BasicPizza(),Pizza::withChickenTikka, Pizza::withProsciutto);
 
//And if you static import PizzaDecorator.bakePizza:
 
String finishedPizza  = bakePizza(new BasicPizza(),Pizza::withChickenTikka, Pizza::withProsciutto);

Как видите, код стал более понятным и лаконичным, и мы не использовали наследование для создания наших декораторов.

Это только один из многих шаблонов проектирования, которые можно улучшить с помощью лямбд. Есть и другие функции, которые можно использовать для улучшения остальных, например, использование частичного приложения (карри) для реализации шаблона адаптера.

Я надеюсь, что вы подумали о принятии более функционального подхода к программированию в вашем стиле разработки.

Библиография

Пример декоратора был вдохновлен « Бандой четырех» — статья « Укрась декоратором»

Метод рефакторинга был вдохновлен следующими докладами Devoxx 2015 (которые я рекомендую посмотреть, поскольку они рассматривают предмет в целом): Шаблон проектирования, загруженный Реми Фораксом , Шаблоны проектирования в свете лямбда-выражений Venkat Subramaniam