С появлением лямбда-выражений в 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
Ссылка: | Шаблон оформления декоратора с использованием лямбд от нашего партнера по JCG Стефана Булзана в блоге Java Advent Calendar . |