Это, кажется, очень распространенный вопрос, хотя, на мой взгляд, у него очень простой ответ.Для меня речь идет о том, как вы видите контракт, который вы разрабатываете, и о том, как вы ожидаете, что он будет реализован и использован.
Как вы заметили, в вашем примере показан неправильный способ использования функциональных интерфейсов.Разработка этих типов только для того, чтобы в итоге вызвать if(dt.test(d))
вместо if(d.isEsMamifero())
, требует простого прилагательного: плохо.И это, вероятно, вменяется в учебники.Проблема в том, что большинство книг учат нас использовать функциональные интерфейсы так, как нас учат использовать интерфейсы / абстракцию в целом, и это не учитывает смысл и большую картину функционального программирования.Конечно, нужно знать, «как» реализовать функциональный интерфейс (в конце концов, это интерфейс), но многие книги не говорят нам, где их применять.
Вот как я объясняю это себе (вбазовые термины):
1 - функциональный интерфейс рассматривается как «именованная логика» (будет реализовано на другой стороне договора)
Да, функционалинтерфейс типа , но имеет больше смысла смотреть на функциональный интерфейс с именем logic .В отличие от обычных типов, таких как Serializable
, Collection
, AutoCloseable
, функциональные интерфейсы, такие как Tester
(или Predicate
), представляют логику (или просто код).Я знаю, что нюансы становятся тонкими, но я полагаю, что есть разница между традиционным абстрактным «типом» ООП и тем, что подразумевается под функциональным интерфейсом.
2 - изолировать код, реализующий функциональныйинтерфейсы из кода, который его потребляет
Проблема использования двух в одном компоненте очевидна в вашем коде.Вы не написали бы функциональный интерфейс, не объявили бы метод, который берет один, все только для того, чтобы реализовать его и передать его своему собственному методу.Если вы делаете это и только это, вы используете абстракцию по неправильным причинам, не говоря уже о правильном использовании функциональных интерфейсов.
Существует множество примеров правильного использования функциональных интерфейсов.Я выберу Collection.forEach
с Consumer
:
Collection<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> System.out.println(s));
Чем это отличается от вашего дизайна?
- Дизайнер
Collection.forEach
останавливается на контракте, который принимаетConsumer
(они не используют Consumer
только для именования / ввода параметра в свой метод Collection.forEach
- операция, для которой требуется настраиваемая логика. Как и в случае сs -> System.out.println(s)
,эта "пользовательская логика" может бытьs -> myList.add(s)
или жеs -> myList.add(s.toUpperCase())
,и т. д., все в соответствии с дизайном клиента кода (задолго до того, как был разработан интерфейс).Метод forEach
интерфейса Collection
организует итерацию и позволяет вызывающей стороне предоставлять логику, вызываемую в каждой итерации.Это разделяет проблемы.
Stream.filter
с Predicate
ближе к вашему примеру, будет хорошей идеей сравнить то, как вы используете Stream.filter
с тем, как вы использовали Tester.test
в своем примере.
С учетом вышесказанного, есть много причин для / против функционального программирования, вышеупомянутое сфокусировано на причине использования (или не использования) функциональных интерфейсов в соответствии с вашим примером (в частности, с точки зрения разработчика, которыйпишет договор).