Фильтр — это шаблон, который следует применять только в определенных случаях. В оригинальном посте я представил очень простой пример, призванный показать, как его применять. В этом посте я представлю гораздо более подробный пример, который также призван объяснить, когда и зачем его применять.
Вступление
Пост состоит из следующих 10 коротких шагов . На каждом этапе я представляю требования следующих двух типов:
- B- *: бизнес — требования (данные владельцем продукта → бесспорный)
- S- * : требования к решениям (вытекающие из выбора решений → спорные )
и я представляю модель Java, отвечающую требованиям, введенным до сих пор. Я делаю это, пока Filterer
не Filterer
в качестве предпочтительного решения.
Итак, позвольте мне взять вас в этом путешествии …
Шаг 1: детектор проблем
Требования № 1
Давайте предположим, что бизнес запрашивает алгоритм для выявления грамматических и орфографических проблем в английских текстах .
Например:
- текст: вы знаете это. → проблемы для обнаружения:
- миг (тип: орфография)
- Текст: я должен проиграть. → проблемы для обнаружения:
- отмечая (тип: орфография)
- потерять (тип: грамматика)
- Текст: я продолжал отмечать, что это свободно. → проблемы для выявления: ∅
Это наше первое деловое требование ( B-1 ).
Простейшей моделью встречи B-1 может быть:
- ввод : простой текст
- вывод : список проблем, где каждая проблема обеспечивает:
- смещения во входном тексте
- тип (грамматика / орфография)
Это наше первое требование к решению ( S-1 ).
Модель Java № 1
Мы можем моделировать S-1 как:
1
2
3
4
|
interface IssueDetector { // e.g. text: "You migth know it." List<Issue> detect(String text); } |
где:
1
2
3
4
5
|
interface Issue { int startOffset(); // e.g. 4 (start of "migth") int endOffset(); // e.g. 9 (end of "migth") IssueType type(); // e.g. SPELLING } |
1
|
enum IssueType { GRAMMAR, SPELLING } |
Это совершить 1 .
Шаг 2: Вероятность
Требования № 2
Однако было бы довольно сложно реализовать настоящий IssueDetector
который работал бы таким детерминированным образом:
- проблема (вероятность P = 100% )
- не проблема (вероятность P = 0% )
Вместо этого IssueDetector
должен быть скорее вероятностным :
- вероятная проблема (вероятность P =? )
Мы можем сохранить различие между проблемой и отсутствием, введя порог вероятности ( PT ):
- проблема (вероятность P ≥ PT ),
- не проблема (вероятность P <PT ).
Тем не менее, стоит адаптировать модель для сохранения вероятности ( P ) — это полезно, например, при рендеринге (более высокая вероятность → более заметный рендеринг).
Подводя итог, наши дополнительные требования решения :
- S-2 : вероятность возникновения проблемы поддержки ( P );
- S-3 : порог вероятности поддержки ( PT ).
Модель Java № 2
Мы можем встретить S-2 , добавив probability()
к Issue
:
1
2
3
4
|
interface Issue { // ... double probability(); } |
Мы можем встретиться с S-3 , добавив probabilityThreshold
IssueDetector
в IssueDetector
:
1
2
3
|
interface IssueDetector { List<Issue> detect(String text, double probabilityThreshold); } |
Это совершить 2 .
Шаг 3: Вероятная проблема
Требования № 3
Предположим, что бизнес требует :
- B-3 : Проверьте все детекторы проблем, используя проверенные тексты английского лингвиста (= нет вероятностей).
Такой корректный текст (или: контрольный пример ) может быть определен как:
- текст, например, вы должны знать это.
- ожидаемые проблемы, например
- shuold (тип: правописание)
Итак, наше решение :
- S-4 : Поддержка ожидаемых проблем (= нет вероятности).
Модель Java № 3
Мы можем встретить S-4 , извлекая подынтерфейс ( ProbableIssue
):
1
2
3
|
interface ProbableIssue extends Issue { double probability(); } |
и возвращая ProbableIssue
из IssueDetector
:
1
2
3
|
interface IssueDetector { List<ProbableIssue> detect(...); } |
Это совершить 3 .
Шаг 4: Выпуск текста
Требования № 4
Предположим, что:
- Все тестовые примеры определены внешне (например, в файлах XML);
- Мы хотим создать параметризованный тест JUnit, где параметры — это тестовые случаи, представленные в виде
Stream
.
Как правило, тестовый пример представляет собой нечто, что мы могли бы назвать проблемным текстом (текст + его проблемы).
Чтобы избежать моделирования проблемного текста как Map.Entry<String, List<Issue>>
(который является расплывчатым и означает недостаточную абстракцию ), давайте введем еще одно требование к решению :
- S-5 : Поддержка текстовых сообщений.
Модель Java № 4
Мы можем моделировать S-5 как:
1
2
3
4
|
interface IssueWiseText { String text(); // e.g. "You migth know it." List<Issue> issues(); // e.g. ["migth"] } |
Это позволяет нам определить Stream
тестовых случаев просто как
-
Stream<IssueWiseText>
вместо
-
Stream<Map.Entry<String, List<Issue>>>
.
Это совершить 4 .
Шаг 5: ожидаемое покрытие
Требования № 5
Предположим, что бизнес требует :
- B-4 : сообщить об ожидаемом освещении проблемы для потока тестовых случаев ;
где освещение вопроса — для простоты — определяется как:
общая длина вопроса
─────────────
общая длина текста
В действительности, освещение проблемы может представлять собой очень сложную бизнес-логику .
Модель Java № 5
Мы можем обработать B-4 с помощью метода на основе Collector
:
1
2
3
|
static double issueCoverage(Stream<? extends IssueWiseText> textStream) { return textStream.collect(IssueCoverage.collector()); } |
Collector
основан на Accumulator
имеющем два изменяемых поля:
1
2
|
int totalIssueLength = 0 ; int totalTextLength = 0 ; |
который для каждого IssueWiseText
мы увеличиваем:
1
2
|
totalIssueLength += issueWiseText.issues().stream().mapToInt(Issue::length). sum (); totalTextLength += issueWiseText.text().length(); |
а затем мы рассчитываем покрытие проблемы как:
1
|
(double) totalIssueLength / totalTextLength |
Это совершить 5 .
Шаг 6: Полученное покрытие
Требования № 6
Предположим, что бизнес требует :
- B-5 : Сообщите о полученном освещении проблемы для всего набора тестов.
где «полученный» означает «рассчитанный с использованием обнаруженных проблем». Теперь все становится интересным!
Прежде всего, поскольку IssueCoverage
представляет бизнес-логику , мы не должны дублировать ее:
- S-6 : Повторно использовать код покрытия проблемы.
Во-вторых, так как метод принимает Stream<? extends IssueWiseText>
Stream<? extends IssueWiseText>
, нам нужно смоделировать IssueWiseText
для ProbableIssue
s:
- S-7 : Поддержка вероятностных текстов, касающихся вопросов.
Я вижу только два варианта:
- Параметризация :
IssueWiseText<I extends Issue>
; - Подтип :
ProbabilisticIssueWiseText extends IssueWiseText
.
Параметрическая модель Java № 6
Параметрическая модель S-7 проста — нам нужно <I extends Issue>
( параметр ограниченного типа ) в IssueWiseText
:
1
2
3
4
|
interface IssueWiseText<I extends Issue> { String text(); List<I> issues(); } |
У этой модели есть недостатки (например, стирание типов ), но она лаконична.
Мы также можем адаптировать IssueDetector
для возврата IssueWiseText<ProbableIssue>
.
Более того, наш Stream
тестовых случаев может превратиться в Stream<IssueWiseText<Issue>>
(хотя IssueWiseText<Issue>
является довольно спорным).
Это совершить 6а .
Подтип Java-модель № 6
Другой вариант — выбрать подтип (который имеет свои недостатки, самым большим из которых может быть дублирование ).
Модель подтипа S-7 использует ковариацию возвращаемого типа :
1
2
3
4
|
interface ProbabilisticIssueWiseText extends IssueWiseText { @Override List<? extends ProbableIssue> issues(); } |
где issues()
в IssueWiseText
должны стать верхними IssueWiseText
( List<? extends Issue>
).
Мы также можем адаптировать IssueDetector
для возврата ProbabilisticIssueWiseText
.
Это совершить 6b .
Шаг 7: Фильтрация по типу проблемы
Требования № 7
Предположим, что бизнес требует :
- B-6 : Сообщить о проблеме по типу проблемы .
Мы могли бы поддержать это, приняв дополнительный параметр типа Predicate <? super Issue>
Predicate <? super Issue>
(параметр IssueType
, будет слишком узким).
Однако его прямая поддержка в IssueCoverage
усложнит бизнес-логику ( commit 7a ‘ ). Вместо этого мы предпочли бы IssueWiseText
отфильтрованные экземпляры IssueWiseText
в IssueCoverage
.
Как мы делаем фильтрацию? Выполнение этого «вручную» (вызов new
самостоятельно) приведет к ненужной связи с реализациями (мы даже еще не знаем их). Вот почему мы позволим IssueWiseText
выполнять фильтрацию (я чувствую, что эта логика здесь):
- S-8 : Поддержка фильтрации по
IssueWiseText
вIssueWiseText
.
Другими словами, мы хотим иметь возможность сказать:
Другими словами, мы хотим иметь возможность сказать:
Эй
IssueWiseText
, отфильтруйте себя поIssue
!
Параметрическая модель Java № 7
В параметрической модели мы добавляем следующий filtered
метод в IssueWiseText<I>
1
|
IssueWiseText<I> filtered(Predicate<? super I> issueFilter); |
Это позволяет нам встретить B-6 как:
1
2
3
|
return textStream .map(text -> text.filtered(issue -> issue.type() == issueType)) .collect(IssueCoverage.collector()); |
Это совершить 7а .
Подтип Java Модель № 7
В модели подтипов мы также добавляем filtered
метод (очень похожий на приведенный выше):
1
|
IssueWiseText filtered(Predicate<? super Issue> issueFilter); |
Это позволяет нам встретить B-6 так же, как и выше.
Это совершить 7b .
Шаг 8: Фильтрация по вероятности
Требования № 8
Предположим, что бизнес требует :
- B-7 : Сообщить о покрытии проблемы с минимальной вероятностью .
Другими словами, бизнес хочет знать, как распределение вероятностей влияет на охват проблемы.
Теперь мы не хотим запускать IssueDetector
с большим количеством различных порогов вероятности ( PT ), потому что это будет очень неэффективно. Вместо этого мы запустим его только один раз (с PT = 0 ), а затем продолжим отбрасывать проблемы с наименьшей вероятностью для пересчета охвата проблемы.
Тем не менее, чтобы иметь возможность фильтровать по вероятностям, нам необходимо:
- S-9 : Поддержка фильтрации по
ProbableIssue
в вероятностном проблемном тексте.
Параметрическая модель Java № 8
В параметрической модели нам не нужно ничего менять. Мы можем встретить B-7 как:
1
2
3
|
return textStream .map(text -> text.filtered(issue -> issue.probability() >= minProbability)) .collect(IssueCoverage.collector()); |
Это совершить 8а .
Подтип Java-модель № 8
В модели подтипов это сложнее, потому что нам нужен дополнительный метод в ProbabilisticIssueWiseText
:
1
|
ProbabilisticIssueWiseText filteredProbabilistic(Predicate<? super ProbableIssue> issueFilter); |
что позволяет нам встретить B-7 как:
1
2
3
|
return textStream .map(text -> text.filteredProbabilistic(issue -> issue.probability() >= minProbability)) .collect(IssueCoverage.collector()); |
Это совершить 8b .
Для меня этот дополнительный метод в ProbabilisticIssueWiseText
довольно тревожный, хотя (см. Здесь ). Вот почему я предлагаю …
Шаг 9: Фильтр
Требования № 9
Поскольку регулярная фильтрация в модели подтипов настолько «неоднородна», давайте сделаем ее равномерной:
- S-10 : Поддержка равномерной фильтрации в модели подтипов текстового вопроса.
Другими словами, мы хотим иметь возможность сказать:
Привет
ProbabilisticIssueWiseText
, отфильтруйте себя поProbableIssue
(но так же, какIssueWiseText
фильтрует себя поIssue
)!
Насколько мне известно, это может быть достигнуто только с помощью шаблона Filterer .
Подтип Java-модель № 9
Поэтому мы применяем универсальный Filterer
к IssueWiseText
:
1
|
Filterer<? extends IssueWiseText, ? extends Issue> filtered(); |
и в ProbablisticIssueWiseText
:
1
2
|
@Override Filterer<? extends ProbabilisticIssueWiseText, ? extends ProbableIssue> filtered(); |
Теперь мы можем фильтровать равномерно, вызывая:
1
|
text.filtered().by(issue -> ...) |
Это совершить 9 .
Шаг 10: Время обнаружения
К этому времени вы должны задаться вопросом, почему я беспокоюсь о модели подтипов, если параметрическая модель намного проще.
Итак, в последний раз, давайте предположим, что бизнес требует :
- B-8 : Время обнаружения отчета (= время, необходимое для обнаружения всех проблем в данном тексте).
Параметрическая модель Java № 10
Я вижу только два способа включения B-8 в параметрическую модель: 1) композиция, 2) подтип.
Композиция для параметрической модели Java № 10
Наносить композицию легко. Мы представляем IssueDetectionResult
:
1
2
3
4
|
interface IssueDetectionResult { IssueWiseText<ProbableIssue> probabilisticIssueWiseText(); Duration detectionTime(); } |
и измените IssueDetector
чтобы вернуть его.
Это совершить 10а .
Подтип для параметрической модели Java № 10
Применение подтипов требует немного больше работы. Нам нужно добавить ProbabilisticIssueWiseText<I>
*
1
2
3
4
|
interface ProbabilisticIssueWiseText<I extends ProbableIssue> extends IssueWiseText<I> { Duration detectionTime(); // ... } |
и измените IssueDetector
чтобы он возвращал ProbabilisticIssueWiseText<?>
.
Это совершить 10а ‘ .
* Обратите внимание, что я оставил <I>
на ProbabilisticIssueWiseText
чтобы не связывать параметризацию с подтипами опасным способом .
Подтип Java Модель № 10
С моделью с чисто подтипами включить B-8 очень легко. Мы просто добавляем detectionTime()
в ProbabilisticIssueAwareText
:
1
2
3
4
|
interface ProbabilisticIssueWiseText extends IssueWiseText { Duration detectionTime(); // ... } |
Это совершить 10b .
Выводы
Там нет времени, чтобы вдаваться в детали (пост уже намного дольше, чем я ожидал).
Тем не менее, я предпочитаю чистый Filterer
(и, следовательно, Filterer
), чем другие решения, потому что:
- Параметризация с композицией оставляет меня без общего супертипа (в некоторых случаях это проблема);
- Параметризация с подтипами имеет слишком много степеней свободы.
Под «слишком многими степенями свободы» я имею в виду, что мне нужно только:
-
IssueAwareText<?>
-
ProbabilisticIssueAwareText<?>
-
IssueAwareText<Issue>
(спорный)
но в коде я тоже столкнусь (говорю из опыта!):
-
IssueAwareText<? extends Issue>
IssueAwareText<? extends Issue>
(избыточная верхняя граница) -
IssueAwareText<ProbableIssue>
-
IssueAwareText<? extends ProbableIssue>
IssueAwareText<? extends ProbableIssue>
(почему неProbabilisticIssueAwareText<?>
?) -
ProbabilisticIssueAwareText<? extends ProbableIssue>
ProbabilisticIssueAwareText<? extends ProbableIssue>
(избыточная верхняя граница) -
ProbabilisticIssueAwareText<ProbableIssue>
так что это слишком запутанно для меня. Но если вы действительно интересуетесь этой темой, посмотрите « Сложный подтип против параметризации» (но будьте осторожны — он даже длиннее, чем этот пост!).
Спасибо за чтение!
Опубликовано на Java Code Geeks с разрешения Томаша Линковски, партнера нашей программы JCG. Смотрите оригинальную статью здесь: Узор Filterer за 10 шагов Мнения, высказанные участниками Java Code Geeks, являются их собственными. |